/**********************************************************************
 * pltcl.c		- PostgreSQL 对 Tcl 作为过程语言 (PL) 的支持
 *
 *	  src/pl/tcl/pltcl.c
 *
 **********************************************************************/

#include "postgres.h"

#include <tcl.h>

#include <unistd.h>
#include <fcntl.h>

#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "fmgr.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "pgstat.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/regproc.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"


PG_MODULE_MAGIC;

#define HAVE_TCL_VERSION(maj,min) \
	((TCL_MAJOR_VERSION > maj) || \
	 (TCL_MAJOR_VERSION == maj && TCL_MINOR_VERSION >= min))

/* 坚持使用 Tcl >= 8.4 */
#if !HAVE_TCL_VERSION(8,4)
#error PostgreSQL only supports Tcl 8.4 or later.
#endif

/* 处理 Tcl 8.6 常量化的黑客方式，以保持兼容性 */
#ifndef CONST86
#define CONST86
#endif

#if !HAVE_TCL_VERSION(8,7)
typedef int Tcl_Size;
#endif

/* 为翻译定义我们的文本域 */
#undef TEXTDOMAIN
#define TEXTDOMAIN PG_TEXTDOMAIN("pltcl")


/*
 * 支持在 UTF8（所有进入或离开 Tcl 的字符串应该是这种形式）和数据库编码之间转换。
 *
 * 如果直接使用 utf_u2e() 或 utf_e2u()，在进行转换时它们会泄漏一些
 * 分配的内存。如果只在每次 PL/Tcl 函数调用中发生一次，这并不值得担心。
 * 如果确实值得担心，请使用包装宏。
 */

static inline char *
fc_utf_u2e(const char *fc_src)
{
	return pg_any_to_server(fc_src, strlen(fc_src), PG_UTF8);
}

static inline char *
fc_utf_e2u(const char *fc_src)
{
	return pg_server_to_any(fc_src, strlen(fc_src), PG_UTF8);
}

#define UTF_BEGIN \
	do { \
		const char *fc__pltcl_utf_src = NULL; \
		char *fc__pltcl_utf_dst = NULL

#define UTF_END \
	if (fc__pltcl_utf_src != (const char *) fc__pltcl_utf_dst) \
			pfree(fc__pltcl_utf_dst); \
	} while (0)

#define UTF_U2E(x) \
	(fc__pltcl_utf_dst = fc_utf_u2e(fc__pltcl_utf_src = (x)))

#define UTF_E2U(x) \
	(fc__pltcl_utf_dst = fc_utf_e2u(fc__pltcl_utf_src = (x)))


/**********************************************************************
 * 与 Tcl 解释器相关的信息。我们有一个解释器用于所有 pltclu（不信任）函数。
 * 对于 pltcl（信任）函数，则为每个有效的 SQL 用户 ID 有一个单独的解释器。
 * （这是为了确保无特权用户无法注入 Tcl 代码，
 * 该代码将在某些其他 SQL 用户的权限下执行。）
 *
 * pltcl_interp_desc 结构体保存在一个 Postgres 哈希表中，以 userid OID 作为索引，
 * OID 0 用于单个不信任的解释器。
 **********************************************************************/
typedef struct pltcl_interp_desc
{
	Oid			user_id;		/* 哈希键（必须是第一个！） */
	Tcl_Interp *interp;			/* 解释器 */
	Tcl_HashTable query_hash;	/* pltcl_query_desc 结构体 */
} pltcl_interp_desc;


/**********************************************************************
 * 我们关于已加载过程的缓存信息
 *
 * pltcl_proc_desc 结构体本身以及所有辅助数据
 * 都存储在由 fn_cxt 字段标识的内存上下文中。
 * 我们可以通过删除该上下文来回收所有数据，当 fn_refcount 变为零时应这样做。
 * （但请注意，我们不打算清理 Tcl 的过程定义副本：在我们替换过程定义时，
 * 管理内存是 Tcl 的问题。当 pg_proc 行被删除时，我们不清理 pltcl_proc_descs，
 * 只在更新时清理，对 Tcl 的副本也适用相同的政策。）
 *
 * 注意，这个结构体中的数据在所有活动调用中是共享的；
 * 除了 fn_refcount 之外，调用实例不应更改任何内容。
 **********************************************************************/
typedef struct pltcl_proc_desc
{
	char	   *user_proname;	/* 用户的名称（来自 pg_proc.proname） */
	char	   *internal_proname;	/* Tcl 名称（基于函数 OID） */
	MemoryContext fn_cxt;		/* 该程序的内存上下文 */
	unsigned long fn_refcount;	/* 活跃引用的数量 */
	TransactionId fn_xmin;		/* pg_proc 行的 xmin */
	ItemPointerData fn_tid;		/* pg_proc 行的 TID */
	bool		fn_readonly;	/* 函数是否为只读？ */
	bool		lanpltrusted;	/* 是 pltcl 吗（相对于 pltclu）？ */
	pltcl_interp_desc *interp_desc; /* 要使用的解释器 */
	Oid			result_typid;	/* 函数结果类型的 OID */
	FmgrInfo	result_in_func; /* 函数结果类型的输入函数 */
	Oid			result_typioparam;	/* 传递给相同的参数 */
	bool		fn_retisset;	/* true if function returns a set */
	bool		fn_retistuple;	/* true if function returns composite */
	bool		fn_retisdomain; /* true if function returns domain */
	void	   *domain_info;	/* opaque cache for domain checks */
	int			nargs;			/* number of arguments */
	/* these arrays have nargs entries: */
	FmgrInfo   *arg_out_func;	/* 参数类型的输出函数 */
	bool	   *arg_is_rowtype; /* 每个参数都是复合类型吗？ */
} pltcl_proc_desc;


/**********************************************************************
 * 我们关于准备和保存计划的缓存信息
 **********************************************************************/
typedef struct pltcl_query_desc
{
	char		qname[20];
	SPIPlanPtr	plan;
	int			nargs;
	Oid		   *argtypes;
	FmgrInfo   *arginfuncs;
	Oid		   *argtypioparams;
} pltcl_query_desc;


/**********************************************************************
 * 为了快速查找，我们维护一个哈希表，从
 * 函数 OID + 触发器标志 + 用户 OID 映射到 pltcl_proc_desc 指针。
 * pltcl_proc_desc 结构不是哈希条目直接组成部分的原因是
 * 简化从 compile_pltcl_function 期间错误的恢复。
 *
 * 注意：如果同一函数在会话中由多个用户 ID 调用，
 * 在 pltcl 函数的情况下，将为每个用户 ID 有一个单独的 pltcl_proc_desc 条目，
 * 但对于 pltclu 函数，只会有一个条目，因为我们
 * 在这种情况下将 user_id 设置为 0。
 **********************************************************************/
typedef struct pltcl_proc_key
{
	Oid			proc_id;		/* 函数 OID */

	/*
	 * is_trigger 实际上是一个布尔值，但声明为 Oid 以确保该结构
	 * 不包含填充
	 */
	Oid			is_trigger;		/* 它是一个触发器函数吗？ */
	Oid			user_id;		/* 调用函数的用户，或 0 */
} pltcl_proc_key;

typedef struct pltcl_proc_ptr
{
	pltcl_proc_key proc_key;	/* 哈希键（必须是第一个！） */
	pltcl_proc_desc *proc_ptr;
} pltcl_proc_ptr;


/**********************************************************************
 * 每次调用状态
 **********************************************************************/
typedef struct pltcl_call_state
{
	/* 调用信息结构，或在触发器中为 NULL */
	FunctionCallInfo fcinfo;

	/* 如果我们在正常（非事件）触发器中，则为触发器数据；否则为 NULL */
	TriggerData *trigdata;

	/* 我们正在执行的函数（如果尚未识别则为 NULL） */
	pltcl_proc_desc *prodesc;

	/*
	 * 用于 SRF 和返回复合类型的函数的信息。
	 * 如果 fn_retistuple 或 fn_retisset 被设置，则 ret_tupdesc 和
	 * attinmeta 被设置，因为即使是标量返回的 SRF 也需要一个元组存储。
	 */
	TupleDesc	ret_tupdesc;	/* 返回行类型，如果 retistuple 或 retisset */
	AttInMetadata *attinmeta;	/* 用于构建该类型元组的元数据 */

	ReturnSetInfo *rsi;			/* 传入的 ReturnSetInfo（如有） */
	Tuplestorestate *tuple_store;	/* SRF 在这里累积结果 */
	MemoryContext tuple_store_cxt;	/* tuplestore 的上下文和资源所有者 */
	ResourceOwner tuple_store_owner;
} pltcl_call_state;


/**********************************************************************
 * 全局数据
 **********************************************************************/
static char *pltcl_start_proc = NULL;
static char *pltclu_start_proc = NULL;
static bool pltcl_pm_init_done = false;
static Tcl_Interp *pltcl_hold_interp = NULL;
static HTAB *pltcl_interp_htab = NULL;
static HTAB *pltcl_proc_htab = NULL;

/* 这是由 pltcl_handler 保存和恢复的 */
static pltcl_call_state *pltcl_current_call_state = NULL;

/**********************************************************************
 * SQLSTATE 条件名称的查找表
 **********************************************************************/
typedef struct
{
	const char *label;
	int			sqlerrstate;
} TclExceptionNameMap;

static const TclExceptionNameMap exception_name_map[] = {
#include "pltclerrcodes.h"		/* pgrminclude 忽略 */
	{NULL, 0}
};

/**********************************************************************
 * 前向声明
 **********************************************************************/
void		_PG_init(void);

static void fc_pltcl_init_interp(pltcl_interp_desc *fc_interp_desc,
							  Oid fc_prolang, bool fc_pltrusted);
static pltcl_interp_desc *fc_pltcl_fetch_interp(Oid fc_prolang, bool fc_pltrusted);
static void fc_call_pltcl_start_proc(Oid fc_prolang, bool fc_pltrusted);
static void fc_start_proc_error_callback(void *fc_arg);

static Datum fc_pltcl_handler(PG_FUNCTION_ARGS, bool fc_pltrusted);

static Datum fc_pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *fc_call_state,
								bool fc_pltrusted);
static HeapTuple fc_pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *fc_call_state,
									   bool fc_pltrusted);
static void fc_pltcl_event_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *fc_call_state,
										bool fc_pltrusted);

static void fc_throw_tcl_error(Tcl_Interp *fc_interp, const char *fc_proname);

static pltcl_proc_desc *fc_compile_pltcl_function(Oid fc_fn_oid, Oid fc_tgreloid,
											   bool fc_is_event_trigger,
											   bool fc_pltrusted);

static int	fc_pltcl_elog(ClientData fc_cdata, Tcl_Interp *fc_interp,
					   int fc_objc, Tcl_Obj *const fc_objv[]);
static void fc_pltcl_construct_errorCode(Tcl_Interp *fc_interp, ErrorData *fc_edata);
static const char *fc_pltcl_get_condition_name(int fc_sqlstate);
static int	fc_pltcl_quote(ClientData fc_cdata, Tcl_Interp *fc_interp,
						int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_argisnull(ClientData fc_cdata, Tcl_Interp *fc_interp,
							int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_returnnull(ClientData fc_cdata, Tcl_Interp *fc_interp,
							 int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_returnnext(ClientData fc_cdata, Tcl_Interp *fc_interp,
							 int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_SPI_execute(ClientData fc_cdata, Tcl_Interp *fc_interp,
							  int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_process_SPI_result(Tcl_Interp *fc_interp,
									 const char *fc_arrayname,
									 Tcl_Obj *fc_loop_body,
									 int fc_spi_rc,
									 SPITupleTable *fc_tuptable,
									 uint64 fc_ntuples);
static int	fc_pltcl_SPI_prepare(ClientData fc_cdata, Tcl_Interp *fc_interp,
							  int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_SPI_execute_plan(ClientData fc_cdata, Tcl_Interp *fc_interp,
								   int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_subtransaction(ClientData fc_cdata, Tcl_Interp *fc_interp,
								 int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_commit(ClientData fc_cdata, Tcl_Interp *fc_interp,
						 int fc_objc, Tcl_Obj *const fc_objv[]);
static int	fc_pltcl_rollback(ClientData fc_cdata, Tcl_Interp *fc_interp,
						   int fc_objc, Tcl_Obj *const fc_objv[]);

static void fc_pltcl_subtrans_begin(MemoryContext fc_oldcontext,
								 ResourceOwner fc_oldowner);
static void fc_pltcl_subtrans_commit(MemoryContext fc_oldcontext,
								  ResourceOwner fc_oldowner);
static void fc_pltcl_subtrans_abort(Tcl_Interp *fc_interp,
								 MemoryContext fc_oldcontext,
								 ResourceOwner fc_oldowner);

static void fc_pltcl_set_tuple_values(Tcl_Interp *fc_interp, const char *fc_arrayname,
								   uint64 fc_tupno, HeapTuple fc_tuple, TupleDesc fc_tupdesc);
static Tcl_Obj *fc_pltcl_build_tuple_argument(HeapTuple fc_tuple, TupleDesc fc_tupdesc, bool fc_include_generated);
static HeapTuple fc_pltcl_build_tuple_result(Tcl_Interp *fc_interp,
										  Tcl_Obj **fc_kvObjv, int fc_kvObjc,
										  pltcl_call_state *fc_call_state);
static void fc_pltcl_init_tuple_store(pltcl_call_state *fc_call_state);


/*
 * 黑客方式覆盖Tcl的内置Notifier子系统。这防止后端变为多线程，这样会破坏各种功能。
 * 如果TCL库已编译为支持多线程（即在Unix下定义了TCL_THREADS，在Windows下的所有情况下），
 * 默认版本的Tcl_InitNotifier会发生这种情况。
 * 禁用通知器是可以的，因为我们从Postgres中永远不会进入Tcl事件循环，
 * 所以通知器功能被初始化但从未使用。只有InitNotifier和DeleteFileHandler似乎会在
 * Postgres中被调用，但我们实现所有函数是为了完整性。
 */
static ClientData
fc_pltcl_InitNotifier(void)
{
	static int	fc_fakeThreadKey;	/* 为ClientData提供有效地址 */

	return (ClientData) &(fc_fakeThreadKey);
}

static void fc_pltcl_FinalizeNotifier(ClientData fc_clientData)
{
}

static void fc_pltcl_SetTimer(CONST86 Tcl_Time *fc_timePtr)
{
}

static void fc_pltcl_AlertNotifier(ClientData fc_clientData)
{
}

static void fc_pltcl_CreateFileHandler(int fc_fd, int fc_mask,
						Tcl_FileProc *fc_proc, ClientData fc_clientData)
{
}

static void fc_pltcl_DeleteFileHandler(int fc_fd)
{
}

static void fc_pltcl_ServiceModeHook(int fc_mode)
{
}

static int fc_pltcl_WaitForEvent(CONST86 Tcl_Time *fc_timePtr)
{
	return 0;
}


/*
 * _PG_init()			- 库加载时初始化
 *
 * 切勿将其设置为静态或更改其名称！
 *
 * 这里完成的工作必须在postmaster进程中是安全的，
 * 以防pltcl库在postmaster中被预加载。
 */
void _PG_init(void)
{
	Tcl_NotifierProcs fc_notifier;
	HASHCTL		fc_hash_ctl;

	/* 确保我们只进行一次初始化（现在应该是多余的） */
	if (pltcl_pm_init_done)
		return;

	pg_bindtextdomain(TEXTDOMAIN);

#ifdef WIN32
	/* 在win32上是必需的，以防加载init.tcl时出错 */
	Tcl_FindExecutable("");
#endif

	/*
	 * 覆盖Notifier子系统中的函数。请参见上面的评论。
	 */
	fc_notifier.setTimerProc = fc_pltcl_SetTimer;
	fc_notifier.waitForEventProc = fc_pltcl_WaitForEvent;
	fc_notifier.createFileHandlerProc = fc_pltcl_CreateFileHandler;
	fc_notifier.deleteFileHandlerProc = fc_pltcl_DeleteFileHandler;
	fc_notifier.initNotifierProc = fc_pltcl_InitNotifier;
	fc_notifier.finalizeNotifierProc = fc_pltcl_FinalizeNotifier;
	fc_notifier.alertNotifierProc = fc_pltcl_AlertNotifier;
	fc_notifier.serviceModeHookProc = fc_pltcl_ServiceModeHook;
	Tcl_SetNotifier(&fc_notifier);

	/************************************************************
	 * 创建虚拟保持解释器，以防在DeleteInterp时关闭
	 * stdout和stderr
	 ************************************************************/
	if ((pltcl_hold_interp = Tcl_CreateInterp()) == NULL)
		elog(ERROR, "could not create dummy Tcl interpreter");
	if (Tcl_Init(pltcl_hold_interp) == TCL_ERROR)
		elog(ERROR, "could not initialize dummy Tcl interpreter");

	/************************************************************
	 * 为工作解释器创建哈希表
	 ************************************************************/
	fc_hash_ctl.keysize = sizeof(Oid);
	fc_hash_ctl.entrysize = sizeof(pltcl_interp_desc);
	pltcl_interp_htab = hash_create("PL/Tcl interpreters",
									8,
									&fc_hash_ctl,
									HASH_ELEM | HASH_BLOBS);

	/************************************************************
	 * 为函数查找创建哈希表
	 ************************************************************/
	fc_hash_ctl.keysize = sizeof(pltcl_proc_key);
	fc_hash_ctl.entrysize = sizeof(pltcl_proc_ptr);
	pltcl_proc_htab = hash_create("PL/Tcl functions",
								  100,
								  &fc_hash_ctl,
								  HASH_ELEM | HASH_BLOBS);

	/************************************************************
	 * 定义PL/Tcl的自定义GUCs
	 ************************************************************/
	DefineCustomStringVariable("pltcl.start_proc",
							   gettext_noop("PL/Tcl function to call once when pltcl is first used."),
							   NULL,
							   &pltcl_start_proc,
							   NULL,
							   PGC_SUSET, 0,
							   NULL, NULL, NULL);
	DefineCustomStringVariable("pltclu.start_proc",
							   gettext_noop("PL/TclU function to call once when pltclu is first used."),
							   NULL,
							   &pltclu_start_proc,
							   NULL,
							   PGC_SUSET, 0,
							   NULL, NULL, NULL);

	MarkGUCPrefixReserved("pltcl");
	MarkGUCPrefixReserved("pltclu");

	pltcl_pm_init_done = true;
}

/**********************************************************************
 * pltcl_init_interp() - 初始化一个新的Tcl解释器
 **********************************************************************/
static void fc_pltcl_init_interp(pltcl_interp_desc *fc_interp_desc, Oid fc_prolang, bool fc_pltrusted)
{
	Tcl_Interp *fc_interp;
	char		fc_interpname[32];

	/************************************************************
	 * 创建Tcl解释器作为pltcl_hold_interp的附属。
	 * 注意：在不受信任的情况下，Tcl会自动执行Tcl_Init，
	 * 而在受信任的情况下这是不需要的。
	 ************************************************************/
	snprintf(fc_interpname, sizeof(fc_interpname), "subsidiary_%u", fc_interp_desc->user_id);
	if ((fc_interp = Tcl_CreateSlave(pltcl_hold_interp, fc_interpname,
								  fc_pltrusted ? 1 : 0)) == NULL)
		elog(ERROR, "could not create subsidiary Tcl interpreter");

	/************************************************************
	 * 初始化与解释器关联的查询哈希表
	 ************************************************************/
	Tcl_InitHashTable(&fc_interp_desc->query_hash, TCL_STRING_KEYS);

	/************************************************************
	 * 在解释器中安装SPI支持的命令
	 ************************************************************/
	Tcl_CreateObjCommand(fc_interp, "elog",
						 fc_pltcl_elog, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "quote",
						 fc_pltcl_quote, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "argisnull",
						 fc_pltcl_argisnull, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "return_null",
						 fc_pltcl_returnnull, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "return_next",
						 fc_pltcl_returnnext, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "spi_exec",
						 fc_pltcl_SPI_execute, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "spi_prepare",
						 fc_pltcl_SPI_prepare, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "spi_execp",
						 fc_pltcl_SPI_execute_plan, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "subtransaction",
						 fc_pltcl_subtransaction, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "commit",
						 fc_pltcl_commit, NULL, NULL);
	Tcl_CreateObjCommand(fc_interp, "rollback",
						 fc_pltcl_rollback, NULL, NULL);

	/************************************************************
	 * 调用适当的 start_proc（如果有的话）。
	 *
	 * 我们必须在调用之前设置 interp_desc->interp，否则 start_proc
	 * 将找不到它应该使用的解释器。但是，如果
	 * start_proc 失败，我们希望放弃使用该解释器。
	 ************************************************************/
	PG_TRY();
	{
		fc_interp_desc->interp = fc_interp;
		fc_call_pltcl_start_proc(fc_prolang, fc_pltrusted);
	}
	PG_CATCH();
	{
		fc_interp_desc->interp = NULL;
		Tcl_DeleteInterp(fc_interp);
		PG_RE_THROW();
	}
	PG_END_TRY();
}

/**********************************************************************
 * pltcl_fetch_interp() - 获取用于函数的 Tcl 解释器
 *
 * 这也处理任何首次使用时所需的初始化。
 **********************************************************************/
static pltcl_interp_desc *
fc_pltcl_fetch_interp(Oid fc_prolang, bool fc_pltrusted)
{
	Oid			fc_user_id;
	pltcl_interp_desc *fc_interp_desc;
	bool		fc_found;

	/* 查找或创建此用户 ID 的解释器哈希表条目 */
	if (fc_pltrusted)
		fc_user_id = GetUserId();
	else
		fc_user_id = InvalidOid;

	fc_interp_desc = hash_search(pltcl_interp_htab, &fc_user_id,
							  HASH_ENTER,
							  &fc_found);
	if (!fc_found)
		fc_interp_desc->interp = NULL;

	/* 如果我们尚未成功创建解释器，尝试做到这一点 */
	if (!fc_interp_desc->interp)
		fc_pltcl_init_interp(fc_interp_desc, fc_prolang, fc_pltrusted);

	return fc_interp_desc;
}


/**********************************************************************
 * call_pltcl_start_proc()	 - 调用用户定义的初始化过程（如果有的话）
 **********************************************************************/
static void fc_call_pltcl_start_proc(Oid fc_prolang, bool fc_pltrusted)
{
	LOCAL_FCINFO(fcinfo, 0);
	char	   *fc_start_proc;
	const char *fc_gucname;
	ErrorContextCallback fc_errcallback;
	List	   *fc_namelist;
	Oid			fc_procOid;
	HeapTuple	fc_procTup;
	Form_pg_proc fc_procStruct;
	AclResult	fc_aclresult;
	FmgrInfo	fc_finfo;
	PgStat_FunctionCallUsage fc_fcusage;

	/* 选择适当的 GUC */
	fc_start_proc = fc_pltrusted ? pltcl_start_proc : pltclu_start_proc;
	fc_gucname = fc_pltrusted ? "pltcl.start_proc" : "pltclu.start_proc";

	/* 如果它是空的或未设置，则不需要做任何事情 */
	if (fc_start_proc == NULL || fc_start_proc[0] == '\0')
		return;

	/* 设置错误上下文回调，以使错误更具帮助性 */
	fc_errcallback.callback = fc_start_proc_error_callback;
	fc_errcallback.arg = unconstify(char *, fc_gucname);
	fc_errcallback.previous = error_context_stack;
	error_context_stack = &fc_errcallback;

	/* 解析可能带有限定符的标识符并查找函数 */
	fc_namelist = stringToQualifiedNameList(fc_start_proc);
	fc_procOid = LookupFuncName(fc_namelist, 0, NULL, false);

	/* 当前用户必须有权限调用该函数 */
	fc_aclresult = pg_proc_aclcheck(fc_procOid, GetUserId(), ACL_EXECUTE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_FUNCTION, fc_start_proc);

	/* 获取函数的 pg_proc 条目 */
	fc_procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_procOid));
	if (!HeapTupleIsValid(fc_procTup))
		elog(ERROR, "cache lookup failed for function %u", fc_procOid);
	fc_procStruct = (Form_pg_proc) GETSTRUCT(fc_procTup);

	/* 它必须与我们当前调用的函数使用相同的语言 */
	if (fc_procStruct->prolang != fc_prolang)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("function \"%s\" is in the wrong language",
						fc_start_proc)));

	/*
	 * 它也不能是 SECURITY DEFINER。这与语言匹配检查
	 * 确保函数将在我们刚完成初始化的相同 Tcl 解释器中执行。
	 */
	if (fc_procStruct->prosecdef)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("function \"%s\" must not be SECURITY DEFINER",
						fc_start_proc)));

	/* 没问题 */
	ReleaseSysCache(fc_procTup);

	/*
	 * 使用正常的 SQL 函数调用机制调用该函数。我们
	 * 可以直接跳到 pltcl_handler()，但这样做似乎
	 * 更好，因为可以将调用暴露给，例如，调用
	 * 统计收集。
	 */
	InvokeFunctionExecuteHook(fc_procOid);
	fmgr_info(fc_procOid, &fc_finfo);
	InitFunctionCallInfoData(*fcinfo, &fc_finfo,
							 0,
							 InvalidOid, NULL, NULL);
	pgstat_init_function_usage(fcinfo, &fc_fcusage);
	(void) FunctionCallInvoke(fcinfo);
	pgstat_end_function_usage(&fc_fcusage, true);

	/* 弹出错误上下文栈 */
	error_context_stack = fc_errcallback.previous;
}

/*
 * 在 start_proc 处理期间发生错误的错误上下文回调。
 */
static void fc_start_proc_error_callback(void *fc_arg)
{
	const char *fc_gucname = (const char *) fc_arg;

	/* 翻译者： %s 是 "pltcl.start_proc" 或 "pltclu.start_proc" */
	errcontext("processing %s parameter", fc_gucname);
}


/**********************************************************************
 * pltcl_call_handler		- 这是 PL 解释器唯一可见的函数。
 *				  PostgreSQL
 *				  函数管理器和触发器管理器
 *				  调用此函数以执行
 *				  PL/Tcl 过程。
 **********************************************************************/
PG_FUNCTION_INFO_V1(pltcl_call_handler);

/* 保持非静态 */
Datum pltcl_call_handler(PG_FUNCTION_ARGS)
{
	return fc_pltcl_handler(fcinfo, true);
}

/*
 * 不安全函数的替代处理程序
 */
PG_FUNCTION_INFO_V1(pltclu_call_handler);

/* 保持非静态 */
Datum pltclu_call_handler(PG_FUNCTION_ARGS)
{
	return fc_pltcl_handler(fcinfo, false);
}


/**********************************************************************
 * pltcl_handler()		- 处理函数和触发器调用的处理程序，
 *						  适用于受信任和不受信任的解释器。
 **********************************************************************/
static Datum fc_pltcl_handler(PG_FUNCTION_ARGS, bool fc_pltrusted)
{
	Datum		fc_retval = (Datum) 0;
	pltcl_call_state fc_current_call_state;
	pltcl_call_state *fc_save_call_state;

	/*
	 * 将 current_call_state 初始化为 null/零；特别是，将其
	 * prodesc 指针设置为 null。任何将其设置为非 null 的操作应
	 * 同时增加 prodesc 的 fn_refcount。我们将在退出此函数时减少
	 * 引用计数，并在不再被引用时删除 prodesc。这确保 prodesc
	 * 在需要时保留，即使在执行过程中有人替换原始 pg_proc 行。
	 */
	memset(&fc_current_call_state, 0, sizeof(fc_current_call_state));

	/*
	 * 确保静态指针正确保存/恢复
	 */
	fc_save_call_state = pltcl_current_call_state;
	pltcl_current_call_state = &fc_current_call_state;

	PG_TRY();
	{
		/*
		 * 确定是作为函数还是触发器调用，并调用适当的
		 * 子处理程序
		 */
		if (CALLED_AS_TRIGGER(fcinfo))
		{
			/* 调用触发器处理程序 */
			fc_retval = PointerGetDatum(fc_pltcl_trigger_handler(fcinfo,
														   &fc_current_call_state,
														   fc_pltrusted));
		}
		else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
		{
			/* 调用事件触发器处理程序 */
			fc_pltcl_event_trigger_handler(fcinfo, &fc_current_call_state, fc_pltrusted);
			fc_retval = (Datum) 0;
		}
		else
		{
			/* 调用常规函数处理程序 */
			fc_current_call_state.fcinfo = fcinfo;
			fc_retval = fc_pltcl_func_handler(fcinfo, &fc_current_call_state, fc_pltrusted);
		}
	}
	PG_FINALLY();
	{
		/* 恢复静态指针，然后清理 prodesc 的引用计数（如果有） */
		/*
		 * （我们是出于谨慎，以防在上下文删除时抛出错误）
		 */
		pltcl_current_call_state = fc_save_call_state;
		if (fc_current_call_state.prodesc != NULL)
		{
			Assert(fc_current_call_state.prodesc->fn_refcount > 0);
			if (--fc_current_call_state.prodesc->fn_refcount == 0)
				MemoryContextDelete(fc_current_call_state.prodesc->fn_cxt);
		}
	}
	PG_END_TRY();

	return fc_retval;
}


/**********************************************************************
 * pltcl_func_handler()		- 常规函数调用的处理程序
 **********************************************************************/
static Datum fc_pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *fc_call_state,
				   bool fc_pltrusted)
{
	bool		fc_nonatomic;
	pltcl_proc_desc *fc_prodesc;
	Tcl_Interp *volatile fc_interp;
	Tcl_Obj    *fc_tcl_cmd;
	int			fc_i;
	int			fc_tcl_rc;
	Datum		fc_retval;

	fc_nonatomic = fcinfo->context &&
		IsA(fcinfo->context, CallContext) &&
		!castNode(CallContext, fcinfo->context)->atomic;

	/* 连接到 SPI 管理器 */
	if (SPI_connect_ext(fc_nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
		elog(ERROR, "could not connect to SPI manager");

	/* 查找或编译函数 */
	fc_prodesc = fc_compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
									 false, fc_pltrusted);

	fc_call_state->prodesc = fc_prodesc;
	fc_prodesc->fn_refcount++;

	fc_interp = fc_prodesc->interp_desc->interp;

	/*
	 * 如果我们是 SRF，检查调用者是否能处理物化模式，并将
	 * 相关信息保存到 call_state 中。我们必须确保返回的
	 * tuplestore 归调用者的上下文拥有，即使我们首先在子事务中
	 * 创建它。
	 */
	if (fc_prodesc->fn_retisset)
	{
		ReturnSetInfo *fc_rsi = (ReturnSetInfo *) fcinfo->resultinfo;

		if (!fc_rsi || !IsA(fc_rsi, ReturnSetInfo))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("set-valued function called in context that cannot accept a set")));

		if (!(fc_rsi->allowedModes & SFRM_Materialize))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("materialize mode required, but it is not allowed in this context")));

		fc_call_state->rsi = fc_rsi;
		fc_call_state->tuple_store_cxt = fc_rsi->econtext->ecxt_per_query_memory;
		fc_call_state->tuple_store_owner = CurrentResourceOwner;
	}

	/************************************************************
	 * 创建 tcl 命令以调用 Tcl 解释器中的内部
	 * proc
	 ************************************************************/
	fc_tcl_cmd = Tcl_NewObj();
	Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
							 Tcl_NewStringObj(fc_prodesc->internal_proname, -1));

	/* 我们持有 tcl_cmd 的引用计数，只是为了确保它可用 */
	Tcl_IncrRefCount(fc_tcl_cmd);

	/************************************************************
	 * 将所有调用参数添加到命令中
	 ************************************************************/
	PG_TRY();
	{
		for (fc_i = 0; fc_i < fc_prodesc->nargs; fc_i++)
		{
			if (fc_prodesc->arg_is_rowtype[fc_i])
			{
				/**************************************************
				 * 对于元组值，添加一个用于 'array set ...' 的列表
				 **************************************************/
				if (fcinfo->args[fc_i].isnull)
					Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, Tcl_NewObj());
				else
				{
					HeapTupleHeader fc_td;
					Oid			fc_tupType;
					int32		fc_tupTypmod;
					TupleDesc	fc_tupdesc;
					HeapTupleData fc_tmptup;
					Tcl_Obj    *fc_list_tmp;

					fc_td = DatumGetHeapTupleHeader(fcinfo->args[fc_i].value);
					/* 提取行类型信息并找到 tupdesc */
					fc_tupType = HeapTupleHeaderGetTypeId(fc_td);
					fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_td);
					fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
					/* 构建一个临时 HeapTuple 控制结构 */
					fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_td);
					fc_tmptup.t_data = fc_td;

					fc_list_tmp = fc_pltcl_build_tuple_argument(&fc_tmptup, fc_tupdesc, true);
					Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, fc_list_tmp);

					ReleaseTupleDesc(fc_tupdesc);
				}
			}
			else
			{
				/**************************************************
				 * 单个值作为其外部表示的字符串元素添加
				 **************************************************/
				if (fcinfo->args[fc_i].isnull)
					Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, Tcl_NewObj());
				else
				{
					char	   *fc_tmp;

					fc_tmp = OutputFunctionCall(&fc_prodesc->arg_out_func[fc_i],
											 fcinfo->args[fc_i].value);
					UTF_BEGIN;
					Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
											 Tcl_NewStringObj(UTF_E2U(fc_tmp), -1));
					UTF_END;
					pfree(fc_tmp);
				}
			}
		}
	}
	PG_CATCH();
	{
		/* 释放引用计数以释放 tcl_cmd */
		Tcl_DecrRefCount(fc_tcl_cmd);
		PG_RE_THROW();
	}
	PG_END_TRY();

	/************************************************************
	 * 调用 Tcl 函数
	 *
	 * 我们假设此调用不会直接抛出 PG 错误。
	 ************************************************************/
	fc_tcl_rc = Tcl_EvalObjEx(fc_interp, fc_tcl_cmd, (TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL));

	/* 释放引用计数以释放 tcl_cmd（和所有附属对象） */
	Tcl_DecrRefCount(fc_tcl_cmd);

	/************************************************************
	 * 检查 Tcl 报告的错误。
	 ************************************************************/
	if (fc_tcl_rc != TCL_OK)
		fc_throw_tcl_error(fc_interp, fc_prodesc->user_proname);

	/************************************************************
	 * 断开与 SPI 管理器的连接，然后创建返回值数据项
	 * （如果输入函数为其进行了 palloc，则不能在 SPI 内存上下文中分配
	 * 因为 SPI_finish 会释放它）。但如果我们被告知返回 NULL，
	 * 则不要尝试调用 result_in_func；
	 * 在这种情况下，Tcl 结果可能不是结果类型的有效值。
	 ************************************************************/
	if (SPI_finish() != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish() failed");

	if (fc_prodesc->fn_retisset)
	{
		ReturnSetInfo *fc_rsi = fc_call_state->rsi;

		/* 我们已经检查过这没问题 */
		fc_rsi->returnMode = SFRM_Materialize;

		/* 如果我们生成了任何元组，则返回结果 */
		if (fc_call_state->tuple_store)
		{
			fc_rsi->setResult = fc_call_state->tuple_store;
			if (fc_call_state->ret_tupdesc)
			{
				MemoryContext fc_oldcxt;

				fc_oldcxt = MemoryContextSwitchTo(fc_call_state->tuple_store_cxt);
				fc_rsi->setDesc = CreateTupleDescCopy(fc_call_state->ret_tupdesc);
				MemoryContextSwitchTo(fc_oldcxt);
			}
		}
		fc_retval = (Datum) 0;
		fcinfo->isnull = true;
	}
	else if (fcinfo->isnull)
	{
		fc_retval = InputFunctionCall(&fc_prodesc->result_in_func,
								   NULL,
								   fc_prodesc->result_typioparam,
								   -1);
	}
	else if (fc_prodesc->fn_retistuple)
	{
		TupleDesc	fc_td;
		HeapTuple	fc_tup;
		Tcl_Obj    *fc_resultObj;
		Tcl_Obj   **fc_resultObjv;
		Tcl_Size	fc_resultObjc;

		/*
		 * 设置有关结果类型的数据。XXX 很诱人考虑
		 * 在 prodesc 中缓存它，在常见情况下，当行类型
		 * 由函数而不是调用查询确定时。但我们必须能够处理
		 * ADD/DROP/ALTER COLUMN 事件，当结果类型是命名复合类型时，
		 * 所以这并不是完全简单的事情。也许有一天值得改进。
		 */
		switch (get_call_result_type(fcinfo, NULL, &fc_td))
		{
			case TYPEFUNC_COMPOSITE:
				/* 成功 */
				break;
			case TYPEFUNC_COMPOSITE_DOMAIN:
				Assert(fc_prodesc->fn_retisdomain);
				break;
			case TYPEFUNC_RECORD:
				/* 未能确定 RECORD 的实际类型 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("function returning record called in context "
								"that cannot accept type record")));
				break;
			default:
				/* 结果类型不是复合类型？ */
				elog(ERROR, "return type must be a row type");
				break;
		}

		Assert(!fc_call_state->ret_tupdesc);
		Assert(!fc_call_state->attinmeta);
		fc_call_state->ret_tupdesc = fc_td;
		fc_call_state->attinmeta = TupleDescGetAttInMetadata(fc_td);

		/* 将函数结果转换为元组 */
		fc_resultObj = Tcl_GetObjResult(fc_interp);
		if (Tcl_ListObjGetElements(fc_interp, fc_resultObj, &fc_resultObjc, &fc_resultObjv) == TCL_ERROR)
			ereport(ERROR,
					(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
					 errmsg("could not parse function return value: %s",
							fc_utf_u2e(Tcl_GetStringResult(fc_interp)))));

		fc_tup = fc_pltcl_build_tuple_result(fc_interp, fc_resultObjv, fc_resultObjc,
									   fc_call_state);
		fc_retval = HeapTupleGetDatum(fc_tup);
	}
	else
		fc_retval = InputFunctionCall(&fc_prodesc->result_in_func,
								   fc_utf_u2e(Tcl_GetStringResult(fc_interp)),
								   fc_prodesc->result_typioparam,
								   -1);

	return fc_retval;
}


/**********************************************************************
 * pltcl_trigger_handler() - 触发器调用的处理程序
 **********************************************************************/
static HeapTuple fc_pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *fc_call_state,
					  bool fc_pltrusted)
{
	pltcl_proc_desc *fc_prodesc;
	Tcl_Interp *volatile fc_interp;
	TriggerData *fc_trigdata = (TriggerData *) fcinfo->context;
	char	   *fc_stroid;
	TupleDesc	fc_tupdesc;
	volatile HeapTuple fc_rettup;
	Tcl_Obj    *fc_tcl_cmd;
	Tcl_Obj    *fc_tcl_trigtup;
	int			fc_tcl_rc;
	int			fc_i;
	const char *fc_result;
	Tcl_Size	fc_result_Objc;
	Tcl_Obj   **fc_result_Objv;
	int			fc_rc PG_USED_FOR_ASSERTS_ONLY;

	fc_call_state->trigdata = fc_trigdata;

	/* 连接到 SPI 管理器 */
	if (SPI_connect() != SPI_OK_CONNECT)
		elog(ERROR, "could not connect to SPI manager");

	/* 使过渡表对该 SPI 连接可见 */
	fc_rc = SPI_register_trigger_data(fc_trigdata);
	Assert(fc_rc >= 0);

	/* 查找或编译函数 */
	fc_prodesc = fc_compile_pltcl_function(fcinfo->flinfo->fn_oid,
									 RelationGetRelid(fc_trigdata->tg_relation),
									 false, /* 不是事件触发器 */
									 fc_pltrusted);

	fc_call_state->prodesc = fc_prodesc;
	fc_prodesc->fn_refcount++;

	fc_interp = fc_prodesc->interp_desc->interp;

	fc_tupdesc = RelationGetDescr(fc_trigdata->tg_relation);

	/************************************************************
	 * 创建 Tcl 命令以调用解释器中的内部
	 * 过程
	 ************************************************************/
	fc_tcl_cmd = Tcl_NewObj();
	Tcl_IncrRefCount(fc_tcl_cmd);

	PG_TRY();
	{
		/* 过程名称（注意这都是 ASCII，因此没有 utf_e2u） */
		Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
								 Tcl_NewStringObj(fc_prodesc->internal_proname, -1));

		/* 用于参数 TG_name 的触发器名称 */
		Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
								 Tcl_NewStringObj(fc_utf_e2u(fc_trigdata->tg_trigger->tgname), -1));

		/* 用于参数 TG_relid 的触发器关系的 oid */
		/* 考虑不转换为字符串以获得更高性能？ */
		fc_stroid = DatumGetCString(DirectFunctionCall1(oidout,
													 ObjectIdGetDatum(fc_trigdata->tg_relation->rd_id)));
		Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
								 Tcl_NewStringObj(fc_stroid, -1));
		pfree(fc_stroid);

		/* 触发器作用于的表的名称：TG_table_name */
		fc_stroid = SPI_getrelname(fc_trigdata->tg_relation);
		Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
								 Tcl_NewStringObj(fc_utf_e2u(fc_stroid), -1));
		pfree(fc_stroid);

		/* 触发器作用于的表的模式：TG_table_schema */
		fc_stroid = SPI_getnspname(fc_trigdata->tg_relation);
		Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
								 Tcl_NewStringObj(fc_utf_e2u(fc_stroid), -1));
		pfree(fc_stroid);

		/* 用于参数 TG_relatts 的属性名称列表 */
		fc_tcl_trigtup = Tcl_NewObj();
		Tcl_ListObjAppendElement(NULL, fc_tcl_trigtup, Tcl_NewObj());
		for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

			if (fc_att->attisdropped)
				Tcl_ListObjAppendElement(NULL, fc_tcl_trigtup, Tcl_NewObj());
			else
				Tcl_ListObjAppendElement(NULL, fc_tcl_trigtup,
										 Tcl_NewStringObj(fc_utf_e2u(NameStr(fc_att->attname)), -1));
		}
		Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, fc_tcl_trigtup);

		/* 事件的 when 部分，用于 TG_when */
		if (TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event))
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
									 Tcl_NewStringObj("BEFORE", -1));
		else if (TRIGGER_FIRED_AFTER(fc_trigdata->tg_event))
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
									 Tcl_NewStringObj("AFTER", -1));
		else if (TRIGGER_FIRED_INSTEAD(fc_trigdata->tg_event))
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
									 Tcl_NewStringObj("INSTEAD OF", -1));
		else
			elog(ERROR, "unrecognized WHEN tg_event: %u", fc_trigdata->tg_event);

		/* 事件的 level 部分，用于 TG_level */
		if (TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
		{
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
									 Tcl_NewStringObj("ROW", -1));

			/*
			 * 现在是事件的命令部分，用于 TG_op 和 NEW
			 * 和 OLD 的数据
			 *
			 * 注意：在 BEFORE 触发器中，存储生成的列尚未
			 * 计算，因此不要在 NEW 行中使其可访问。
			 */
			if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
			{
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("INSERT", -1));

				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 fc_pltcl_build_tuple_argument(fc_trigdata->tg_trigtuple,
																	fc_tupdesc,
																	!TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event)));
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, Tcl_NewObj());

				fc_rettup = fc_trigdata->tg_trigtuple;
			}
			else if (TRIGGER_FIRED_BY_DELETE(fc_trigdata->tg_event))
			{
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("DELETE", -1));

				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, Tcl_NewObj());
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 fc_pltcl_build_tuple_argument(fc_trigdata->tg_trigtuple,
																	fc_tupdesc,
																	true));

				fc_rettup = fc_trigdata->tg_trigtuple;
			}
			else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
			{
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("UPDATE", -1));

				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 fc_pltcl_build_tuple_argument(fc_trigdata->tg_newtuple,
																	fc_tupdesc,
																	!TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event)));
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 fc_pltcl_build_tuple_argument(fc_trigdata->tg_trigtuple,
																	fc_tupdesc,
																	true));

				fc_rettup = fc_trigdata->tg_newtuple;
			}
			else
				elog(ERROR, "unrecognized OP tg_event: %u", fc_trigdata->tg_event);
		}
		else if (TRIGGER_FIRED_FOR_STATEMENT(fc_trigdata->tg_event))
		{
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
									 Tcl_NewStringObj("STATEMENT", -1));

			if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("INSERT", -1));
			else if (TRIGGER_FIRED_BY_DELETE(fc_trigdata->tg_event))
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("DELETE", -1));
			else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("UPDATE", -1));
			else if (TRIGGER_FIRED_BY_TRUNCATE(fc_trigdata->tg_event))
				Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
										 Tcl_NewStringObj("TRUNCATE", -1));
			else
				elog(ERROR, "unrecognized OP tg_event: %u", fc_trigdata->tg_event);

			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, Tcl_NewObj());
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd, Tcl_NewObj());

			fc_rettup = (HeapTuple) NULL;
		}
		else
			elog(ERROR, "unrecognized LEVEL tg_event: %u", fc_trigdata->tg_event);

		/* 最后附加来自 CREATE TRIGGER 的参数 */
		for (fc_i = 0; fc_i < fc_trigdata->tg_trigger->tgnargs; fc_i++)
			Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
									 Tcl_NewStringObj(fc_utf_e2u(fc_trigdata->tg_trigger->tgargs[fc_i]), -1));
	}
	PG_CATCH();
	{
		Tcl_DecrRefCount(fc_tcl_cmd);
		PG_RE_THROW();
	}
	PG_END_TRY();

	/************************************************************
	 * 调用 Tcl 函数
	 *
	 * 我们假设此调用不会直接抛出 PG 错误。
	 ************************************************************/
	fc_tcl_rc = Tcl_EvalObjEx(fc_interp, fc_tcl_cmd, (TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL));

	/* 释放引用计数以释放 tcl_cmd（和所有附属对象） */
	Tcl_DecrRefCount(fc_tcl_cmd);

	/************************************************************
	 * 检查 Tcl 报告的错误。
	 ************************************************************/
	if (fc_tcl_rc != TCL_OK)
		fc_throw_tcl_error(fc_interp, fc_prodesc->user_proname);

	/************************************************************
	 * 退出 SPI 环境。
	 ************************************************************/
	if (SPI_finish() != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish() failed");

	/************************************************************
	 * 程序的返回值可能是魔法字符串 OK 或 SKIP，或者是 array get 返回的列表。
	 * 我们可以检查 OK 或 SKIP，而不必担心编码问题。
	 ************************************************************/
	fc_result = Tcl_GetStringResult(fc_interp);

	if (strcmp(fc_result, "OK") == 0)
		return fc_rettup;
	if (strcmp(fc_result, "SKIP") == 0)
		return (HeapTuple) NULL;

	/************************************************************
	 * 否则，返回值应为指定要返回的修改元组的列名/值列表。
	 ************************************************************/
	if (Tcl_ListObjGetElements(fc_interp, Tcl_GetObjResult(fc_interp),
							   &fc_result_Objc, &fc_result_Objv) != TCL_OK)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("could not parse trigger return value: %s",
						fc_utf_u2e(Tcl_GetStringResult(fc_interp)))));

	/* 将函数结果转换为元组 */
	fc_rettup = fc_pltcl_build_tuple_result(fc_interp, fc_result_Objv, fc_result_Objc,
									  fc_call_state);

	return fc_rettup;
}

/**********************************************************************
 * pltcl_event_trigger_handler()	- 事件触发调用的处理程序
 **********************************************************************/
static void fc_pltcl_event_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *fc_call_state,
							bool fc_pltrusted)
{
	pltcl_proc_desc *fc_prodesc;
	Tcl_Interp *volatile fc_interp;
	EventTriggerData *fc_tdata = (EventTriggerData *) fcinfo->context;
	Tcl_Obj    *fc_tcl_cmd;
	int			fc_tcl_rc;

	/* 连接到 SPI 管理器 */
	if (SPI_connect() != SPI_OK_CONNECT)
		elog(ERROR, "could not connect to SPI manager");

	/* 查找或编译函数 */
	fc_prodesc = fc_compile_pltcl_function(fcinfo->flinfo->fn_oid,
									 InvalidOid, true, fc_pltrusted);

	fc_call_state->prodesc = fc_prodesc;
	fc_prodesc->fn_refcount++;

	fc_interp = fc_prodesc->interp_desc->interp;

	/* 创建 tcl 命令并调用内部 proc */
	fc_tcl_cmd = Tcl_NewObj();
	Tcl_IncrRefCount(fc_tcl_cmd);
	Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
							 Tcl_NewStringObj(fc_prodesc->internal_proname, -1));
	Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
							 Tcl_NewStringObj(fc_utf_e2u(fc_tdata->event), -1));
	Tcl_ListObjAppendElement(NULL, fc_tcl_cmd,
							 Tcl_NewStringObj(fc_utf_e2u(GetCommandTagName(fc_tdata->tag)),
											  -1));

	fc_tcl_rc = Tcl_EvalObjEx(fc_interp, fc_tcl_cmd, (TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL));

	/* 释放引用计数以释放 tcl_cmd（和所有附属对象） */
	Tcl_DecrRefCount(fc_tcl_cmd);

	/* 检查 Tcl 报告的错误。 */
	if (fc_tcl_rc != TCL_OK)
		fc_throw_tcl_error(fc_interp, fc_prodesc->user_proname);

	if (SPI_finish() != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish() failed");
}


/**********************************************************************
 * throw_tcl_error	- 记录从 Tcl 解释器返回的错误
 *
 * 注意：仅用于报告由 Tcl_EvalObjEx() 或
 * 其他 Tcl_Eval() 变体返回的错误。其他函数可能不会填充 "errorInfo"，
 * 因此它可能未设置或甚至包含之前某个错误的详细信息。
 **********************************************************************/
static void fc_throw_tcl_error(Tcl_Interp *fc_interp, const char *fc_proname)
{
	/*
	 * 在这里需要谨慎，因为 Tcl_GetVar 可能会覆盖
	 * 解释器结果（即使实际上不应该如此），而且我们
	 * 无法控制 ereport 参数的评估顺序。因此，在调用
	 * Tcl_GetVar 之前，要确保拥有结果字符串的副本。
	 */
	char	   *fc_emsg;
	char	   *fc_econtext;

	fc_emsg = pstrdup(fc_utf_u2e(Tcl_GetStringResult(fc_interp)));
	fc_econtext = fc_utf_u2e(Tcl_GetVar(fc_interp, "errorInfo", TCL_GLOBAL_ONLY));
	ereport(ERROR,
			(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
			 errmsg("%s", fc_emsg),
			 errcontext("%s\nin PL/Tcl function \"%s\"",
						fc_econtext, fc_proname)));
}


/**********************************************************************
 * compile_pltcl_function	- 编译（或希望只是查找）函数
 *
 * tgreloid 是编译触发器时关系的 OID，或者在编译普通函数时为零
 * (InvalidOid)。
 **********************************************************************/
static pltcl_proc_desc *
fc_compile_pltcl_function(Oid fc_fn_oid, Oid fc_tgreloid,
					   bool fc_is_event_trigger, bool fc_pltrusted)
{
	HeapTuple	fc_procTup;
	Form_pg_proc fc_procStruct;
	pltcl_proc_key fc_proc_key;
	pltcl_proc_ptr *fc_proc_ptr;
	bool		fc_found;
	pltcl_proc_desc *fc_prodesc;
	pltcl_proc_desc *fc_old_prodesc;
	volatile MemoryContext fc_proc_cxt = NULL;
	Tcl_DString fc_proc_internal_def;
	Tcl_DString fc_proc_internal_body;

	/* 无论如何我们都需要 pg_proc 元组... */
	fc_procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_fn_oid));
	if (!HeapTupleIsValid(fc_procTup))
		elog(ERROR, "cache lookup failed for function %u", fc_fn_oid);
	fc_procStruct = (Form_pg_proc) GETSTRUCT(fc_procTup);

	/*
	 * 在 pltcl_proc_htab 中查找函数；如果不存在，创建一个条目
	 * 并将条目的 proc_ptr 设置为 NULL。
	 */
	fc_proc_key.proc_id = fc_fn_oid;
	fc_proc_key.is_trigger = OidIsValid(fc_tgreloid);
	fc_proc_key.user_id = fc_pltrusted ? GetUserId() : InvalidOid;

	fc_proc_ptr = hash_search(pltcl_proc_htab, &fc_proc_key,
						   HASH_ENTER,
						   &fc_found);
	if (!fc_found)
		fc_proc_ptr->proc_ptr = NULL;

	fc_prodesc = fc_proc_ptr->proc_ptr;

	/************************************************************
	 * 如果存在，必须检查它是否仍然是最新的。
	 * 这个检查是必要的，因为 CREATE OR REPLACE FUNCTION 可以修改
	 * 函数的 pg_proc 条目而不改变其 OID。
	 ************************************************************/
/* 它仍然是最新的，因此我们可以使用它 */
/************************************************************
	 * 如果我们在哈希表中没有找到它，我们分析
	 * 函数的参数和返回类型，并存储
	 * 输入/输出函数在 prodesc 块中，并为它创建
	 * 一个新的哈希表条目。
	 *
	 * 然后我们将过程加载到 Tcl 解释器中。
	 ************************************************************/
/************************************************************
		 * 根据函数的 Oid 构建我们的内部 proc 名称。
		 * 在适当的时候附加 "_trigger"，以确保正常和触发
		 * 情况分开。请注意，名称必须是全 ASCII。
		 ************************************************************/
/************************************************************
		 * 分配一个上下文，用于保存过程的所有 PG 数据。
		 ************************************************************/
/************************************************************
		 * 分配并填写一个新的过程描述块。
		 * struct prodesc 和附属数据必须都在 proc_cxt 中。
		 ************************************************************/
/* 记住函数是否为 STABLE/IMMUTABLE */
/* 以及它是否是可信的 */
/************************************************************
		 * 确定用于该函数的解释器
		 ************************************************************/
/************************************************************
		 * 获取返回值输入转换所需的信息。
		 ************************************************************/
/* 不允许伪类型结果，除了 VOID 和 RECORD */
/* 可以 */
	/************************************************************
		 * 获取所有过程参数输出转换所需的信息， 
		 * 并设定参数命名信息。
		 ************************************************************/
/* 不允许伪类型参数，除了 RECORD */
/* 触发过程具有固定参数 */
/* 事件触发过程具有固定参数 */
/************************************************************
		 * 创建 tcl 命令以定义内部
		 * 过程
		 *
		 * 将此代码保留为 DString - 这里性能不关键，
		 * 我们不想重复嵌入在 Tcl_DStringAppendElement 中的
		 * Tcl 引号规则的知识。
		 ************************************************************/
	if (fc_prodesc != NULL &&
		fc_prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(fc_procTup->t_data) &&
		ItemPointerEquals(&fc_prodesc->fn_tid, &fc_procTup->t_self))
	{
		/************************************************************
		 * 在过程体前加上
		 * upvar #0 <internal_proname> GD
		 * 并适当设置参数
		 ************************************************************/
		ReleaseSysCache(fc_procTup);
		return fc_prodesc;
	}

	/* 事件触发器不支持参数 */
	Tcl_DStringInit(&fc_proc_internal_def);
	Tcl_DStringInit(&fc_proc_internal_body);
	PG_TRY();
	{
		bool		fc_is_trigger = OidIsValid(fc_tgreloid);
		char		fc_internal_proname[128];
		HeapTuple	fc_typeTup;
		Form_pg_type fc_typeStruct;
		char		fc_proc_internal_args[33 * FUNC_MAX_ARGS];
		Datum		fc_prosrcdatum;
		bool		fc_isnull;
		char	   *fc_proc_source;
		char		fc_buf[48];
		Tcl_Interp *fc_interp;
		int			fc_i;
		int			fc_tcl_rc;
		MemoryContext fc_oldcontext;

		/************************************************************
		 * 将用户的函数定义添加到过程体中
		 ************************************************************/
		if (fc_is_event_trigger)
			snprintf(fc_internal_proname, sizeof(fc_internal_proname),
					 "__PLTcl_proc_%u_evttrigger", fc_fn_oid);
		else if (fc_is_trigger)
			snprintf(fc_internal_proname, sizeof(fc_internal_proname),
					 "__PLTcl_proc_%u_trigger", fc_fn_oid);
		else
			snprintf(fc_internal_proname, sizeof(fc_internal_proname),
					 "__PLTcl_proc_%u", fc_fn_oid);

		/************************************************************
		 * 分配一个上下文，以保存过程的所有 PG 数据。
		 ************************************************************/
		fc_proc_cxt = AllocSetContextCreate(TopMemoryContext,
										 "PL/Tcl function",
										 ALLOCSET_SMALL_SIZES);

		/************************************************************
		 * 分配并填充一个新的过程描述块。
		 * struct prodesc 及其附属数据必须都存储在 proc_cxt 中。
		 ************************************************************/
		fc_oldcontext = MemoryContextSwitchTo(fc_proc_cxt);
		fc_prodesc = (pltcl_proc_desc *) palloc0(sizeof(pltcl_proc_desc));
		fc_prodesc->user_proname = pstrdup(NameStr(fc_procStruct->proname));
		MemoryContextSetIdentifier(fc_proc_cxt, fc_prodesc->user_proname);
		fc_prodesc->internal_proname = pstrdup(fc_internal_proname);
		fc_prodesc->fn_cxt = fc_proc_cxt;
		fc_prodesc->fn_refcount = 0;
		fc_prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(fc_procTup->t_data);
		fc_prodesc->fn_tid = fc_procTup->t_self;
		fc_prodesc->nargs = fc_procStruct->pronargs;
		fc_prodesc->arg_out_func = (FmgrInfo *) palloc0(fc_prodesc->nargs * sizeof(FmgrInfo));
		fc_prodesc->arg_is_rowtype = (bool *) palloc0(fc_prodesc->nargs * sizeof(bool));
		MemoryContextSwitchTo(fc_oldcontext);

		/* 记住函数是否为 STABLE/IMMUTABLE */
		fc_prodesc->fn_readonly =
			(fc_procStruct->provolatile != PROVOLATILE_VOLATILE);
		/**********************************************************************
 * pltcl_elog()		- PLTcl的elog()支持
 **********************************************************************/
		fc_prodesc->lanpltrusted = fc_pltrusted;

		/*
		 * 我们只是将错误传递回Tcl。如果没有捕获，它最终将
		 * 在我们到达调用处理程序时转换为PG错误。
		 */
		fc_prodesc->interp_desc = fc_pltcl_fetch_interp(fc_procStruct->prolang,
												  fc_prodesc->lanpltrusted);
		fc_interp = fc_prodesc->interp_desc->interp;

		/************************************************************
		 * 获取返回值输入转换所需的信息。
		 ************************************************************/
		if (!fc_is_trigger && !fc_is_event_trigger)
		{
			Oid			fc_rettype = fc_procStruct->prorettype;

			fc_typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_rettype));
			if (!HeapTupleIsValid(fc_typeTup))
				elog(ERROR, "cache lookup failed for type %u", fc_rettype);
			fc_typeStruct = (Form_pg_type) GETSTRUCT(fc_typeTup);

			/* 必须重置elog.c的状态 */
			if (fc_typeStruct->typtype == TYPTYPE_PSEUDO)
			{
				if (fc_rettype == VOIDOID ||
					fc_rettype == RECORDOID)
					 /* 可以 */ ;
				else if (fc_rettype == TRIGGEROID ||
						 fc_rettype == EVENT_TRIGGEROID)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("trigger functions can only be called as triggers")));
				else
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("PL/Tcl functions cannot return type %s",
									format_type_be(fc_rettype))));
			}

			fc_prodesc->result_typid = fc_rettype;
			fmgr_info_cxt(fc_typeStruct->typinput,
						  &(fc_prodesc->result_in_func),
						  fc_proc_cxt);
			fc_prodesc->result_typioparam = getTypeIOParam(fc_typeTup);

			fc_prodesc->fn_retisset = fc_procStruct->proretset;
			fc_prodesc->fn_retistuple = type_is_rowtype(fc_rettype);
			fc_prodesc->fn_retisdomain = (fc_typeStruct->typtype == TYPTYPE_DOMAIN);
			fc_prodesc->domain_info = NULL;

			ReleaseSysCache(fc_typeTup);
		}

		/**********************************************************************
 * pltcl_construct_errorCode()		- 从PostgreSQL服务器构造带有详细信息的Tcl错误代码列表
 **********************************************************************/
		if (!fc_is_trigger && !fc_is_event_trigger)
		{
			fc_proc_internal_args[0] = '\0';
			for (fc_i = 0; fc_i < fc_prodesc->nargs; fc_i++)
			{
				Oid			fc_argtype = fc_procStruct->proargtypes.values[fc_i];

				fc_typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_argtype));
				if (!HeapTupleIsValid(fc_typeTup))
					elog(ERROR, "cache lookup failed for type %u", fc_argtype);
				fc_typeStruct = (Form_pg_type) GETSTRUCT(fc_typeTup);

				/* 不允许伪类型参数，除了 RECORD */
				if (fc_typeStruct->typtype == TYPTYPE_PSEUDO &&
					fc_argtype != RECORDOID)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("PL/Tcl functions cannot accept type %s",
									format_type_be(fc_argtype))));

				if (type_is_rowtype(fc_argtype))
				{
					fc_prodesc->arg_is_rowtype[fc_i] = true;
					snprintf(fc_buf, sizeof(fc_buf), "__PLTcl_Tup_%d", fc_i + 1);
				}
				else
				{
					fc_prodesc->arg_is_rowtype[fc_i] = false;
					fmgr_info_cxt(fc_typeStruct->typoutput,
								  &(fc_prodesc->arg_out_func[fc_i]),
								  fc_proc_cxt);
					snprintf(fc_buf, sizeof(fc_buf), "%d", fc_i + 1);
				}

				if (fc_i > 0)
					strcat(fc_proc_internal_args, " ");
				strcat(fc_proc_internal_args, fc_buf);

				ReleaseSysCache(fc_typeTup);
			}
		}
		else if (fc_is_trigger)
		{
			/**********************************************************************
 * pltcl_get_condition_name()	- 查找SQLSTATE的名称
 **********************************************************************/
			strcpy(fc_proc_internal_args,
				   "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
		}
		else if (fc_is_event_trigger)
		{
			/**********************************************************************
 * pltcl_quote()	- 对将要在 SPI_execute 查询字符串中使用的文字字符串进行引用
 **********************************************************************/
			strcpy(fc_proc_internal_args, "TG_event TG_tag");
		}

		/************************************************************
	 * 检查调用语法
	 ************************************************************/
		Tcl_DStringAppendElement(&fc_proc_internal_def, "proc");
		Tcl_DStringAppendElement(&fc_proc_internal_def, fc_internal_proname);
		Tcl_DStringAppendElement(&fc_proc_internal_def, fc_proc_internal_args);

		/************************************************************
	 * 为字符串可能增长到的最大空间分配内存并初始化指针
	 ************************************************************/
		Tcl_DStringAppend(&fc_proc_internal_body, "upvar #0 ", -1);
		Tcl_DStringAppend(&fc_proc_internal_body, fc_internal_proname, -1);
		Tcl_DStringAppend(&fc_proc_internal_body, " GD\n", -1);
		if (fc_is_trigger)
		{
			Tcl_DStringAppend(&fc_proc_internal_body,
							  "array set NEW $__PLTcl_Tup_NEW\n", -1);
			Tcl_DStringAppend(&fc_proc_internal_body,
							  "array set OLD $__PLTcl_Tup_OLD\n", -1);
			Tcl_DStringAppend(&fc_proc_internal_body,
							  "set i 0\n"
							  "set v 0\n"
							  "foreach v $args {\n"
							  "  incr i\n"
							  "  set $i $v\n"
							  "}\n"
							  "unset i v\n\n", -1);
		}
		else if (fc_is_event_trigger)
		{
			/************************************************************
	 * 遍历字符串，将每个引号和反斜杠翻倍
	 ************************************************************/
		}
		else
		{
			for (fc_i = 0; fc_i < fc_prodesc->nargs; fc_i++)
			{
				if (fc_prodesc->arg_is_rowtype[fc_i])
				{
					snprintf(fc_buf, sizeof(fc_buf),
							 "array set %d $__PLTcl_Tup_%d\n",
							 fc_i + 1, fc_i + 1);
					Tcl_DStringAppend(&fc_proc_internal_body, fc_buf, -1);
				}
			}
		}

		/************************************************************
	 * 终止字符串并将其设为结果
	 ************************************************************/
		fc_prosrcdatum = SysCacheGetAttr(PROCOID, fc_procTup,
									  Anum_pg_proc_prosrc, &fc_isnull);
		if (fc_isnull)
			elog(ERROR, "null prosrc");
		fc_proc_source = TextDatumGetCString(fc_prosrcdatum);
		UTF_BEGIN;
		Tcl_DStringAppend(&fc_proc_internal_body, UTF_E2U(fc_proc_source), -1);
		UTF_END;
		pfree(fc_proc_source);
		Tcl_DStringAppendElement(&fc_proc_internal_def,
								 Tcl_DStringValue(&fc_proc_internal_body));

		/**********************************************************************
 * pltcl_argisnull()	- 确定特定参数是否为 NULL
 **********************************************************************/
		fc_tcl_rc = Tcl_EvalEx(fc_interp,
							Tcl_DStringValue(&fc_proc_internal_def),
							Tcl_DStringLength(&fc_proc_internal_def),
							TCL_EVAL_GLOBAL);
		if (fc_tcl_rc != TCL_OK)
			ereport(ERROR,
					(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
					 errmsg("could not create internal procedure \"%s\": %s",
							fc_internal_proname,
							fc_utf_u2e(Tcl_GetStringResult(fc_interp)))));
	}
	PG_CATCH();
	{
		/************************************************************
	 * 检查调用语法
	 ************************************************************/
		if (fc_proc_cxt)
			MemoryContextDelete(fc_proc_cxt);
		Tcl_DStringFree(&fc_proc_internal_def);
		Tcl_DStringFree(&fc_proc_internal_body);
		PG_RE_THROW();
	}
	PG_END_TRY();

	/************************************************************
	 * 检查我们是否以正常函数的形式被调用
	 ************************************************************/
	fc_old_prodesc = fc_proc_ptr->proc_ptr;

	fc_proc_ptr->proc_ptr = fc_prodesc;
	fc_prodesc->fn_refcount++;

	if (fc_old_prodesc != NULL)
	{
		Assert(fc_old_prodesc->fn_refcount > 0);
		if (--fc_old_prodesc->fn_refcount == 0)
			MemoryContextDelete(fc_old_prodesc->fn_cxt);
	}

	Tcl_DStringFree(&fc_proc_internal_def);
	Tcl_DStringFree(&fc_proc_internal_body);

	ReleaseSysCache(fc_procTup);

	return fc_prodesc;
}


/************************************************************
	 * 获取参数编号
	 ************************************************************/
static int fc_pltcl_elog(ClientData fc_cdata, Tcl_Interp *fc_interp,
		   int fc_objc, Tcl_Obj *const fc_objv[])
{
	volatile int fc_level;
	MemoryContext fc_oldcontext;
	int			fc_priIndex;

	static const char *fc_logpriorities[] = {
		"DEBUG", "LOG", "INFO", "NOTICE",
		"WARNING", "ERROR", "FATAL", (const char *) NULL
	};

	static const int fc_loglevels[] = {
		DEBUG2, LOG, INFO, NOTICE,
		WARNING, ERROR, FATAL
	};

	if (fc_objc != 3)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "level msg");
		return TCL_ERROR;
	}

	if (Tcl_GetIndexFromObj(fc_interp, fc_objv[1], fc_logpriorities, "priority",
							TCL_EXACT, &fc_priIndex) != TCL_OK)
		return TCL_ERROR;

	fc_level = fc_loglevels[fc_priIndex];

	if (fc_level == ERROR)
	{
		/************************************************************
	 * 检查 argno 是否有效
	 ************************************************************/
		Tcl_SetObjResult(fc_interp, fc_objv[2]);
		return TCL_ERROR;
	}

	/************************************************************
	 * 获取请求的 NULL 状态
	 ************************************************************/
	fc_oldcontext = CurrentMemoryContext;
	PG_TRY();
	{
		UTF_BEGIN;
		ereport(fc_level,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
				 errmsg("%s", UTF_U2E(Tcl_GetString(fc_objv[2])))));
		UTF_END;
	}
	PG_CATCH();
	{
		ErrorData  *fc_edata;

		/* 必须重置 elog.c 的状态 */
		MemoryContextSwitchTo(fc_oldcontext);
		fc_edata = CopyErrorData();
		FlushErrorState();

		/************************************************************
	 * 检查调用语法
	 ************************************************************/
		fc_pltcl_construct_errorCode(fc_interp, fc_edata);
		UTF_BEGIN;
		Tcl_SetObjResult(fc_interp, Tcl_NewStringObj(UTF_E2U(fc_edata->message), -1));
		UTF_END;
		FreeErrorData(fc_edata);

		return TCL_ERROR;
	}
	PG_END_TRY();

	return TCL_OK;
}


/************************************************************
	 * 检查我们是否以正常函数的形式被调用
	 ************************************************************/
static void fc_pltcl_construct_errorCode(Tcl_Interp *fc_interp, ErrorData *fc_edata)
{
	Tcl_Obj    *fc_obj = Tcl_NewObj();

	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj("POSTGRES", -1));
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj(PG_VERSION, -1));
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj("SQLSTATE", -1));
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj(unpack_sql_state(fc_edata->sqlerrcode), -1));
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj("condition", -1));
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj(fc_pltcl_get_condition_name(fc_edata->sqlerrcode), -1));
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj("message", -1));
	UTF_BEGIN;
	Tcl_ListObjAppendElement(fc_interp, fc_obj,
							 Tcl_NewStringObj(UTF_E2U(fc_edata->message), -1));
	UTF_END;
	if (fc_edata->detail)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("detail", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->detail), -1));
		UTF_END;
	}
	if (fc_edata->hint)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("hint", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->hint), -1));
		UTF_END;
	}
	if (fc_edata->context)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("context", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->context), -1));
		UTF_END;
	}
	if (fc_edata->schema_name)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("schema", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->schema_name), -1));
		UTF_END;
	}
	if (fc_edata->table_name)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("table", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->table_name), -1));
		UTF_END;
	}
	if (fc_edata->column_name)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("column", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->column_name), -1));
		UTF_END;
	}
	if (fc_edata->datatype_name)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("datatype", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->datatype_name), -1));
		UTF_END;
	}
	if (fc_edata->constraint_name)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("constraint", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->constraint_name), -1));
		UTF_END;
	}
	/************************************************************
	 * 设置 NULL 返回标志并导致 Tcl 从过程返回。
	 ************************************************************/
	if (fc_edata->internalquery)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("statement", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->internalquery), -1));
		UTF_END;
	}
	if (fc_edata->internalpos > 0)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("cursor_position", -1));
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewIntObj(fc_edata->internalpos));
	}
	if (fc_edata->filename)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("filename", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->filename), -1));
		UTF_END;
	}
	if (fc_edata->lineno > 0)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("lineno", -1));
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewIntObj(fc_edata->lineno));
	}
	if (fc_edata->funcname)
	{
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj("funcname", -1));
		UTF_BEGIN;
		Tcl_ListObjAppendElement(fc_interp, fc_obj,
								 Tcl_NewStringObj(UTF_E2U(fc_edata->funcname), -1));
		UTF_END;
	}

	Tcl_SetObjErrorCode(fc_interp, fc_obj);
}


/**********************************************************************
 * pltcl_returnnext()	- 向 SRF 的结果元组存储添加一行。
 **********************************************************************/
static const char * fc_pltcl_get_condition_name(int fc_sqlstate)
{
	int			fc_i;

	for (fc_i = 0; exception_name_map[fc_i].label != NULL; fc_i++)
	{
		if (exception_name_map[fc_i].sqlerrstate == fc_sqlstate)
			return exception_name_map[fc_i].label;
	}
	return "unrecognized_sqlstate";
}


/*
	 * 检查我们是否被作为一个返回集合的函数调用
	 */
static int fc_pltcl_quote(ClientData fc_cdata, Tcl_Interp *fc_interp,
			int fc_objc, Tcl_Obj *const fc_objv[])
{
	char	   *fc_tmp;
	const char *fc_cp1;
	char	   *fc_cp2;
	Tcl_Size	fc_length;

	/*
	 * 检查调用语法
	 */
	if (fc_objc != 2)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "string");
		return TCL_ERROR;
	}

	/*
	 * 其余部分可能会抛出 elog(ERROR)，因此必须在子事务中运行。
	 *
	 * 使用子事务的一个小优点是它提供了一个短期内的内存上下文，因此我们无需担心内存泄漏。
	 * 要使用该上下文，请直接调用 BeginInternalSubTransaction，而不是通过 pltcl_subtrans_begin。
	 */
	fc_cp1 = Tcl_GetStringFromObj(fc_objv[1], &fc_length);
	fc_tmp = palloc(fc_length * 2 + 1);
	fc_cp2 = fc_tmp;

	/* 如果是第一个输出行，则设置元组存储 */
	while (*fc_cp1)
	{
		if (*fc_cp1 == '\'')
			*fc_cp2++ = '\'';
		else
		{
			if (*fc_cp1 == '\\')
				*fc_cp2++ = '\\';
		}
		*fc_cp2++ = *fc_cp1++;
	}

	/* 结果应该是一个列表，因此将其拆分 */
	*fc_cp2 = '\0';
	Tcl_SetObjResult(fc_interp, Tcl_NewStringObj(fc_tmp, -1));
	pfree(fc_tmp);
	return TCL_OK;
}


/* 为了谨慎起见，检查 tupdesc 是否恰好有一列 */
static int fc_pltcl_argisnull(ClientData fc_cdata, Tcl_Interp *fc_interp,
				int fc_objc, Tcl_Obj *const fc_objv[])
{
	int			fc_argno;
	FunctionCallInfo fcinfo = pltcl_current_call_state->fcinfo;

	/*
	 * 检查调用语法
	 */
	if (fc_objc != 2)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "argno");
		return TCL_ERROR;
	}

	/* 想要在函数的内存上下文中运行 */
	if (fcinfo == NULL)
	{
		Tcl_SetObjResult(fc_interp,
						 Tcl_NewStringObj("argisnull cannot be used in triggers", -1));
		return TCL_ERROR;
	}

	/* 提交内事务，返回到外部 xact 上下文 */
	if (Tcl_GetIntFromObj(fc_interp, fc_objv[1], &fc_argno) != TCL_OK)
		return TCL_ERROR;

	/* 保存错误信息 */
	fc_argno--;
	if (fc_argno < 0 || fc_argno >= fcinfo->nargs)
	{
		Tcl_SetObjResult(fc_interp,
						 Tcl_NewStringObj("argno out of range", -1));
		return TCL_ERROR;
	}

	/* 中止内事务 */
	Tcl_SetObjResult(fc_interp, Tcl_NewBooleanObj(PG_ARGISNULL(fc_argno)));
	return TCL_OK;
}


/* 将错误数据传递给 Tcl */
static int fc_pltcl_returnnull(ClientData fc_cdata, Tcl_Interp *fc_interp,
				 int fc_objc, Tcl_Obj *const fc_objv[])
{
	FunctionCallInfo fc_fcinfo = pltcl_current_call_state->fcinfo;

	/*
	 * 检查调用语法
	 */
	if (fc_objc != 1)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "");
		return TCL_ERROR;
	}

	/* 想要在函数的内存上下文中运行 */
	if (fc_fcinfo == NULL)
	{
		Tcl_SetObjResult(fc_interp,
						 Tcl_NewStringObj("return_null cannot be used in triggers", -1));
		return TCL_ERROR;
	}

	/************************************************************
	 * 在子事务中执行查询，以便我们能够理智地处理错误
	 ************************************************************/
	fc_fcinfo->isnull = true;

	return TCL_RETURN;
}


/*
 * 处理来自 SPI_execute 或 SPI_execute_plan 的结果
 *
 * pltcl_SPI_execute 和 pltcl_SPI_execute_plan 之间的共享代码
 */
static int fc_pltcl_returnnext(ClientData fc_cdata, Tcl_Interp *fc_interp,
				 int fc_objc, Tcl_Obj *const fc_objv[])
{
	pltcl_call_state *fc_call_state = pltcl_current_call_state;
	FunctionCallInfo fc_fcinfo = fc_call_state->fcinfo;
	pltcl_proc_desc *fc_prodesc = fc_call_state->prodesc;
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	ResourceOwner fc_oldowner = CurrentResourceOwner;
	volatile int fc_result = TCL_OK;

	/* 用于返回元组的实用程序的传递 */
	if (fc_fcinfo == NULL)
	{
		Tcl_SetObjResult(fc_interp,
						 Tcl_NewStringObj("return_next cannot be used in triggers", -1));
		return TCL_ERROR;
	}

	if (!fc_prodesc->fn_retisset)
	{
		Tcl_SetObjResult(fc_interp,
						 Tcl_NewStringObj("return_next cannot be used in non-set-returning functions", -1));
		return TCL_ERROR;
	}

	/************************************************************
	 * 通过其键获取准备好的计划描述符
	 ************************************************************/
	if (fc_objc != 2)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "fc_result");
		return TCL_ERROR;
	}

	/*
			 * 处理我们获得的元组
			 */
	BeginInternalSubTransaction(NULL);
	PG_TRY();
	{
		/*
				 * 如果没有给出循环体，则仅从第一个元组设置变量（如果有的话）
				 */
		if (fc_call_state->tuple_store == NULL)
			fc_pltcl_init_tuple_store(fc_call_state);

		if (fc_prodesc->fn_retistuple)
		{
			Tcl_Obj   **fc_rowObjv;
			Tcl_Size	fc_rowObjc;

			/*
				 * 有一个循环体 - 处理所有元组并在每个元组上评估主体
				 */
			if (Tcl_ListObjGetElements(fc_interp, fc_objv[1], &fc_rowObjc, &fc_rowObjv) == TCL_ERROR)
				fc_result = TCL_ERROR;
			else
			{
				HeapTuple	fc_tuple;

				fc_tuple = fc_pltcl_build_tuple_result(fc_interp, fc_rowObjv, fc_rowObjc,
												 fc_call_state);
				tuplestore_puttuple(fc_call_state->tuple_store, fc_tuple);
			}
		}
		else
		{
			Datum		fc_retval;
			bool		fc_isNull = false;

			/**********************************************************************
 * pltcl_SPI_prepare()		- 内置支持为预编译计划
 *				  Tcl 命令 SPI_prepare
 *				  始终使用 SPI_keepplan 保存计划并返回一个
 *				  用于访问的键。目前没有机会准备而不保存计划。
 **********************************************************************/
			if (fc_call_state->ret_tupdesc->natts != 1)
				elog(ERROR, "wrong result type supplied in return_next");

			fc_retval = InputFunctionCall(&fc_prodesc->result_in_func,
									   fc_utf_u2e((char *) Tcl_GetString(fc_objv[1])),
									   fc_prodesc->result_typioparam,
									   -1);
			tuplestore_putvalues(fc_call_state->tuple_store, fc_call_state->ret_tupdesc,
								 &fc_retval, &fc_isNull);
		}

		fc_pltcl_subtrans_commit(fc_oldcontext, fc_oldowner);
	}
	PG_CATCH();
	{
		fc_pltcl_subtrans_abort(fc_interp, fc_oldcontext, fc_oldowner);
		return TCL_ERROR;
	}
	PG_END_TRY();

	return fc_result;
}


/************************************************************
	 * 检查调用语法
	 ************************************************************/
static void fc_pltcl_subtrans_begin(MemoryContext fc_oldcontext, ResourceOwner fc_oldowner)
{
	BeginInternalSubTransaction(NULL);

	/* 希望在函数的内存上下文中运行 */
	MemoryContextSwitchTo(fc_oldcontext);
}

static void fc_pltcl_subtrans_commit(MemoryContext fc_oldcontext, ResourceOwner fc_oldowner)
{
	/* 提交内部事务，返回外部事务上下文 */
	ReleaseCurrentSubTransaction();
	MemoryContextSwitchTo(fc_oldcontext);
	CurrentResourceOwner = fc_oldowner;
}

static void fc_pltcl_subtrans_abort(Tcl_Interp *fc_interp,
					 MemoryContext fc_oldcontext, ResourceOwner fc_oldowner)
{
	ErrorData  *fc_edata;

	/* 保存错误信息 */
	MemoryContextSwitchTo(fc_oldcontext);
	fc_edata = CopyErrorData();
	FlushErrorState();

	/* 中止内部事务 */
	RollbackAndReleaseCurrentSubTransaction();
	MemoryContextSwitchTo(fc_oldcontext);
	CurrentResourceOwner = fc_oldowner;

	/************************************************************
	 * 检查调用语法
	 ************************************************************/
	fc_pltcl_construct_errorCode(fc_interp, fc_edata);
	UTF_BEGIN;
	Tcl_SetObjResult(fc_interp, Tcl_NewStringObj(UTF_E2U(fc_edata->message), -1));
	UTF_END;
	FreeErrorData(fc_edata);
}


/************************************************************
		 * 将计划保存到永久内存中（现在它在
		 * SPI procCxt 中，该上下文将在函数结束时消失）。
		 ************************************************************/
static int fc_pltcl_SPI_execute(ClientData fc_cdata, Tcl_Interp *fc_interp,
				  int fc_objc, Tcl_Obj *const fc_objv[])
{
	int			fc_my_rc;
	int			fc_spi_rc;
	int			fc_query_idx;
	int			fc_i;
	int			fc_optIndex;
	int			fc_count = 0;
	const char *volatile fc_arrayname = NULL;
	Tcl_Obj    *volatile fc_loop_body = NULL;
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	ResourceOwner fc_oldowner = CurrentResourceOwner;

	enum options
	{
		OPT_ARRAY, OPT_COUNT
	};

	static const char *fc_options[] = {
		"-array", "-count", (const char *) NULL
	};

	/************************************************************
	 * 为计划插入一个哈希表条目，并将
	 * 键返回给调用者
	 ************************************************************/
	if (fc_objc < 2)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv,
						 "?-count n? ?-array name? query ?loop body?");
		return TCL_ERROR;
	}

	fc_i = 1;
	while (fc_i < fc_objc)
	{
		if (Tcl_GetIndexFromObj(NULL, fc_objv[fc_i], fc_options, NULL,
								TCL_EXACT, &fc_optIndex) != TCL_OK)
			break;

		if (++fc_i >= fc_objc)
		{
			Tcl_SetObjResult(fc_interp,
							 Tcl_NewStringObj("missing argument to -count or -array", -1));
			return TCL_ERROR;
		}

		switch ((enum options) fc_optIndex)
		{
			case OPT_ARRAY:
				fc_arrayname = Tcl_GetString(fc_objv[fc_i++]);
				break;

			case OPT_COUNT:
				if (Tcl_GetIntFromObj(fc_interp, fc_objv[fc_i++], &fc_count) != TCL_OK)
					return TCL_ERROR;
				break;
		}
	}

	fc_query_idx = fc_i;
	if (fc_query_idx >= fc_objc || fc_query_idx + 2 < fc_objc)
	{
		Tcl_WrongNumArgs(fc_interp, fc_query_idx - 1, fc_objv, "query ?loop body?");
		return TCL_ERROR;
	}

	if (fc_query_idx + 1 < fc_objc)
		fc_loop_body = fc_objv[fc_query_idx + 1];

	/* qname 是 ASCII，因此无需进行编码转换 */

	fc_pltcl_subtrans_begin(fc_oldcontext, fc_oldowner);

	PG_TRY();
	{
		UTF_BEGIN;
		fc_spi_rc = SPI_execute(UTF_U2E(Tcl_GetString(fc_objv[fc_query_idx])),
							 pltcl_current_call_state->prodesc->fn_readonly, fc_count);
		UTF_END;

		fc_my_rc = fc_pltcl_process_SPI_result(fc_interp,
										 fc_arrayname,
										 fc_loop_body,
										 fc_spi_rc,
										 SPI_tuptable,
										 SPI_processed);

		fc_pltcl_subtrans_commit(fc_oldcontext, fc_oldowner);
	}
	PG_CATCH();
	{
		fc_pltcl_subtrans_abort(fc_interp, fc_oldcontext, fc_oldowner);
		return TCL_ERROR;
	}
	PG_END_TRY();

	return fc_my_rc;
}

/**********************************************************************
 * pltcl_SPI_execute_plan()		- 执行准备好的计划
 **********************************************************************/
static int fc_pltcl_process_SPI_result(Tcl_Interp *fc_interp,
						 const char *fc_arrayname,
						 Tcl_Obj *fc_loop_body,
						 int fc_spi_rc,
						 SPITupleTable *fc_tuptable,
						 uint64 fc_ntuples)
{
	int			fc_my_rc = TCL_OK;
	int			fc_loop_rc;
	HeapTuple  *fc_tuples;
	TupleDesc	fc_tupdesc;

	switch (fc_spi_rc)
	{
		case SPI_OK_SELINTO:
		case SPI_OK_INSERT:
		case SPI_OK_DELETE:
		case SPI_OK_UPDATE:
		case SPI_OK_MERGE:
			Tcl_SetObjResult(fc_interp, Tcl_NewWideIntObj(fc_ntuples));
			break;

		case SPI_OK_UTILITY:
		case SPI_OK_REWRITTEN:
			if (fc_tuptable == NULL)
			{
				Tcl_SetObjResult(fc_interp, Tcl_NewIntObj(0));
				break;
			}
			/************************************************************
	 * 获取选项并检查语法
	 ************************************************************/
			/************************************************************
	 * 通过其键获取准备好的计划描述符
	 ************************************************************/

		case SPI_OK_SELECT:
		case SPI_OK_INSERT_RETURNING:
		case SPI_OK_DELETE_RETURNING:
		case SPI_OK_UPDATE_RETURNING:

			/************************************************************
	 * 如果给定了 nulls 字符串，检查长度是否正确
	 ************************************************************/
			fc_tuples = fc_tuptable->vals;
			fc_tupdesc = fc_tuptable->tupdesc;

			if (fc_loop_body == NULL)
			{
				/************************************************************
	 * 如果在准备时有参数类型列表，现在需要
	 * 一个参数值列表
	 ************************************************************/
				if (fc_ntuples > 0)
					fc_pltcl_set_tuple_values(fc_interp, fc_arrayname, 0,
										   fc_tuples[0], fc_tupdesc);
			}
			else
			{
				/************************************************************
		 * 拆分参数值
		 ************************************************************/
				uint64		fc_i;

				for (fc_i = 0; fc_i < fc_ntuples; fc_i++)
				{
					fc_pltcl_set_tuple_values(fc_interp, fc_arrayname, fc_i,
										   fc_tuples[fc_i], fc_tupdesc);

					fc_loop_rc = Tcl_EvalObjEx(fc_interp, fc_loop_body, 0);

					if (fc_loop_rc == TCL_OK)
						continue;
					if (fc_loop_rc == TCL_CONTINUE)
						continue;
					if (fc_loop_rc == TCL_RETURN)
					{
						fc_my_rc = TCL_RETURN;
						break;
					}
					if (fc_loop_rc == TCL_BREAK)
						break;
					fc_my_rc = TCL_ERROR;
					break;
				}
			}

			if (fc_my_rc == TCL_OK)
			{
				Tcl_SetObjResult(fc_interp, Tcl_NewWideIntObj(fc_ntuples));
			}
			break;

		default:
			Tcl_AppendResult(fc_interp, "pltcl: SPI_execute failed: ",
							 SPI_result_code_string(fc_spi_rc), NULL);
			fc_my_rc = TCL_ERROR;
			break;
	}

	SPI_freetuptable(fc_tuptable);

	return fc_my_rc;
}


/************************************************************
		 * 检查参数数量是否匹配
		 ************************************************************/
static int fc_pltcl_SPI_prepare(ClientData fc_cdata, Tcl_Interp *fc_interp,
				  int fc_objc, Tcl_Obj *const fc_objv[])
{
	volatile MemoryContext fc_plan_cxt = NULL;
	Tcl_Size	fc_nargs;
	Tcl_Obj   **fc_argsObj;
	pltcl_query_desc *fc_qdesc;
	int			fc_i;
	Tcl_HashEntry *fc_hashent;
	int			fc_hashnew;
	Tcl_HashTable *fc_query_hash;
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	ResourceOwner fc_oldowner = CurrentResourceOwner;

	/************************************************************
	 * 获取循环主体（如果存在）
	 ************************************************************/
	if (fc_objc != 3)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "query argtypes");
		return TCL_ERROR;
	}

	/************************************************************
	 * 在子事务中执行计划，以便我们可以合理地处理错误
	 ************************************************************/
	if (Tcl_ListObjGetElements(fc_interp, fc_objv[2], &fc_nargs, &fc_argsObj) != TCL_OK)
		return TCL_ERROR;

	/************************************************************
		 * 使用类型特定的输入函数为 SPI_execute_plan() 设置值数组
		 ************************************************************/
	fc_plan_cxt = AllocSetContextCreate(TopMemoryContext,
									 "PL/Tcl spi_prepare query",
									 ALLOCSET_SMALL_SIZES);
	MemoryContextSwitchTo(fc_plan_cxt);
	fc_qdesc = (pltcl_query_desc *) palloc0(sizeof(pltcl_query_desc));
	snprintf(fc_qdesc->qname, sizeof(fc_qdesc->qname), "%p", fc_qdesc);
	fc_qdesc->nargs = fc_nargs;
	fc_qdesc->argtypes = (Oid *) palloc(fc_nargs * sizeof(Oid));
	fc_qdesc->arginfuncs = (FmgrInfo *) palloc(fc_nargs * sizeof(FmgrInfo));
	fc_qdesc->argtypioparams = (Oid *) palloc(fc_nargs * sizeof(Oid));
	MemoryContextSwitchTo(fc_oldcontext);

	/************************************************************
		 * 执行计划
		 ************************************************************/

	fc_pltcl_subtrans_begin(fc_oldcontext, fc_oldowner);

	PG_TRY();
	{
		/************************************************************
		 * 解析参数类型名称，然后根据 oid 在系统缓存中查找，并记住输入转换所需的信息。
		 ************************************************************/
		for (fc_i = 0; fc_i < fc_nargs; fc_i++)
		{
			Oid			fc_typId,
						fc_typInput,
						fc_typIOParam;
			int32		fc_typmod;

			parseTypeString(Tcl_GetString(fc_argsObj[fc_i]), &fc_typId, &fc_typmod, false);

			getTypeInputInfo(fc_typId, &fc_typInput, &fc_typIOParam);

			fc_qdesc->argtypes[fc_i] = fc_typId;
			fmgr_info_cxt(fc_typInput, &(fc_qdesc->arginfuncs[fc_i]), fc_plan_cxt);
			fc_qdesc->argtypioparams[fc_i] = fc_typIOParam;
		}

		/************************************************************
		 * 准备计划并检查错误
		 ************************************************************/
		UTF_BEGIN;
		fc_qdesc->plan = SPI_prepare(UTF_U2E(Tcl_GetString(fc_objv[1])),
								  fc_nargs, fc_qdesc->argtypes);
		UTF_END;

		if (fc_qdesc->plan == NULL)
			elog(ERROR, "SPI_prepare() failed");

		/************************************************************
		 * 将计划保存到永久内存中（现在它位于 SPI procCxt 中，该上下文将在函数结束时消失）。
		 ************************************************************/
		if (SPI_keepplan(fc_qdesc->plan))
			elog(ERROR, "SPI_keepplan() failed");

		fc_pltcl_subtrans_commit(fc_oldcontext, fc_oldowner);
	}
	PG_CATCH();
	{
		fc_pltcl_subtrans_abort(fc_interp, fc_oldcontext, fc_oldowner);

		MemoryContextDelete(fc_plan_cxt);

		return TCL_ERROR;
	}
	PG_END_TRY();

	/* 提交子事务 */
	fc_query_hash = &pltcl_current_call_state->prodesc->interp_desc->query_hash;

	fc_hashent = Tcl_CreateHashEntry(fc_query_hash, fc_qdesc->qname, &fc_hashnew);
	Tcl_SetHashValue(fc_hashent, (ClientData) fc_qdesc);

	/* 在任何情况下，恢复先前的内存上下文和资源拥有者 */
	Tcl_SetObjResult(fc_interp, Tcl_NewStringObj(fc_qdesc->qname, -1));
	return TCL_OK;
}


/**********************************************************************
 * pltcl_commit()
 *
 * 提交事务并启动一个新事务。
 **********************************************************************/
static int fc_pltcl_SPI_execute_plan(ClientData fc_cdata, Tcl_Interp *fc_interp,
					   int fc_objc, Tcl_Obj *const fc_objv[])
{
	int			fc_my_rc;
	int			fc_spi_rc;
	int			fc_i;
	int			fc_j;
	int			fc_optIndex;
	Tcl_HashEntry *fc_hashent;
	pltcl_query_desc *fc_qdesc;
	const char *fc_nulls = NULL;
	const char *fc_arrayname = NULL;
	Tcl_Obj    *fc_loop_body = NULL;
	int			fc_count = 0;
	Tcl_Size	fc_callObjc;
	Tcl_Obj   **fc_callObjv = NULL;
	Datum	   *fc_argvalues;
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	ResourceOwner fc_oldowner = CurrentResourceOwner;
	Tcl_HashTable *fc_query_hash;

	enum options
	{
		OPT_ARRAY, OPT_COUNT, OPT_NULLS
	};

	static const char *fc_options[] = {
		"-array", "-count", "-nulls", (const char *) NULL
	};

	/* 保存错误信息 */
	fc_i = 1;
	while (fc_i < fc_objc)
	{
		if (Tcl_GetIndexFromObj(NULL, fc_objv[fc_i], fc_options, NULL,
								TCL_EXACT, &fc_optIndex) != TCL_OK)
			break;

		if (++fc_i >= fc_objc)
		{
			Tcl_SetObjResult(fc_interp,
							 Tcl_NewStringObj("missing argument to -array, -count or -nulls", -1));
			return TCL_ERROR;
		}

		switch ((enum options) fc_optIndex)
		{
			case OPT_ARRAY:
				fc_arrayname = Tcl_GetString(fc_objv[fc_i++]);
				break;

			case OPT_COUNT:
				if (Tcl_GetIntFromObj(fc_interp, fc_objv[fc_i++], &fc_count) != TCL_OK)
					return TCL_ERROR;
				break;

			case OPT_NULLS:
				fc_nulls = Tcl_GetString(fc_objv[fc_i++]);
				break;
		}
	}

	/* 将错误数据传递给 Tcl */
	if (fc_i >= fc_objc)
	{
		Tcl_SetObjResult(fc_interp,
						 Tcl_NewStringObj("missing argument to -count or -array", -1));
		return TCL_ERROR;
	}

	fc_query_hash = &pltcl_current_call_state->prodesc->interp_desc->query_hash;

	fc_hashent = Tcl_FindHashEntry(fc_query_hash, Tcl_GetString(fc_objv[fc_i]));
	if (fc_hashent == NULL)
	{
		Tcl_AppendResult(fc_interp, "invalid queryid '", Tcl_GetString(fc_objv[fc_i]), "'", NULL);
		return TCL_ERROR;
	}
	fc_qdesc = (pltcl_query_desc *) Tcl_GetHashValue(fc_hashent);
	fc_i++;

	/**********************************************************************
 * pltcl_rollback()
 *
 * 中止事务并启动一个新事务。
 **********************************************************************/
	if (fc_nulls != NULL)
	{
		if (strlen(fc_nulls) != fc_qdesc->nargs)
		{
			Tcl_SetObjResult(fc_interp,
							 Tcl_NewStringObj("length of nulls string doesn't match number of arguments",
											  -1));
			return TCL_ERROR;
		}
	}

	/* 保存错误信息 */
	if (fc_qdesc->nargs > 0)
	{
		if (fc_i >= fc_objc)
		{
			Tcl_SetObjResult(fc_interp,
							 Tcl_NewStringObj("argument list length doesn't match number of arguments for query",
											  -1));
			return TCL_ERROR;
		}

		/* 将错误数据传递给 Tcl */
		if (Tcl_ListObjGetElements(fc_interp, fc_objv[fc_i++], &fc_callObjc, &fc_callObjv) != TCL_OK)
			return TCL_ERROR;

		/**********************************************************************
 * pltcl_set_tuple_values() - 为给定元组的所有属性设置变量
 *
 * 注意：arrayname 假定为 UTF8；它通常来自 Tcl
 **********************************************************************/
		if (fc_callObjc != fc_qdesc->nargs)
		{
			Tcl_SetObjResult(fc_interp,
							 Tcl_NewStringObj("argument list length doesn't match number of arguments for query",
											  -1));
			return TCL_ERROR;
		}
	}
	else
		fc_callObjc = 0;

	/************************************************************
	 * 为下面的 Tcl_SetVar2Ex() 准备指针
	 ************************************************************/
	if (fc_i < fc_objc)
		fc_loop_body = fc_objv[fc_i++];

	if (fc_i != fc_objc)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv,
						 "?-count n? ?-array name? ?-nulls string? "
						 "query ?args? ?loop body?");
		return TCL_ERROR;
	}

	/*
		 * 当输出到数组时，用当前元组编号填充 ".tupno" 元素。
		 * 如果 ".tupno" 在行类型中作为实际字段名称使用，将在下面被覆盖。
		 */

	fc_pltcl_subtrans_begin(fc_oldcontext, fc_oldowner);

	PG_TRY();
	{
		/* 忽略丢弃的属性 */
		fc_argvalues = (Datum *) palloc(fc_callObjc * sizeof(Datum));

		for (fc_j = 0; fc_j < fc_callObjc; fc_j++)
		{
			if (fc_nulls && fc_nulls[fc_j] == 'n')
			{
				fc_argvalues[fc_j] = InputFunctionCall(&fc_qdesc->arginfuncs[fc_j],
												 NULL,
												 fc_qdesc->argtypioparams[fc_j],
												 -1);
			}
			else
			{
				UTF_BEGIN;
				fc_argvalues[fc_j] = InputFunctionCall(&fc_qdesc->arginfuncs[fc_j],
												 UTF_U2E(Tcl_GetString(fc_callObjv[fc_j])),
												 fc_qdesc->argtypioparams[fc_j],
												 -1);
				UTF_END;
			}
		}

		/************************************************************
		 * 获取属性名称
************************************************************/
		fc_spi_rc = SPI_execute_plan(fc_qdesc->plan, fc_argvalues, fc_nulls,
								  pltcl_current_call_state->prodesc->fn_readonly,
								  fc_count);

		fc_my_rc = fc_pltcl_process_SPI_result(fc_interp,
										 fc_arrayname,
										 fc_loop_body,
										 fc_spi_rc,
										 SPI_tuptable,
										 SPI_processed);

		fc_pltcl_subtrans_commit(fc_oldcontext, fc_oldowner);
	}
	PG_CATCH();
	{
		fc_pltcl_subtrans_abort(fc_interp, fc_oldcontext, fc_oldowner);
		return TCL_ERROR;
	}
	PG_END_TRY();

	return fc_my_rc;
}


/************************************************************
		 * 获取属性值
************************************************************/
static int fc_pltcl_subtransaction(ClientData fc_cdata, Tcl_Interp *fc_interp,
					 int fc_objc, Tcl_Obj *const fc_objv[])
{
	MemoryContext fc_oldcontext = CurrentMemoryContext;
	ResourceOwner fc_oldowner = CurrentResourceOwner;
	int			fc_retcode;

	if (fc_objc != 2)
	{
		Tcl_WrongNumArgs(fc_interp, 1, fc_objv, "command");
		return TCL_ERROR;
	}

	/************************************************************
		 * 如果有值，设置变量
		 * 如果没有，解除它
		 *
		 * 嗯 - 空属性会导致函数崩溃，如果它们没有预期到 - 这里需要更聪明的解决方案。
************************************************************/
	BeginInternalSubTransaction(NULL);
	MemoryContextSwitchTo(fc_oldcontext);

	fc_retcode = Tcl_EvalObjEx(fc_interp, fc_objv[1], 0);

	if (fc_retcode == TCL_ERROR)
	{
		/**********************************************************************
 * pltcl_build_tuple_argument() - 从给定元组的所有属性构建一个可用于 'array set' 的列表对象
**********************************************************************/
		RollbackAndReleaseCurrentSubTransaction();
	}
	else
	{
		/* 忽略被丢弃的属性 */
		ReleaseCurrentSubTransaction();
	}

	/* 除非请求，否则不包括 */
	MemoryContextSwitchTo(fc_oldcontext);
	CurrentResourceOwner = fc_oldowner;

	return fc_retcode;
}


/************************************************************
		 * 获取属性名称
************************************************************/
static int fc_pltcl_commit(ClientData fc_cdata, Tcl_Interp *fc_interp,
			 int fc_objc, Tcl_Obj *const fc_objv[])
{
	MemoryContext fc_oldcontext = CurrentMemoryContext;

	PG_TRY();
	{
		SPI_commit();
	}
	PG_CATCH();
	{
		ErrorData  *fc_edata;

		/* 保存错误信息 */
		MemoryContextSwitchTo(fc_oldcontext);
		fc_edata = CopyErrorData();
		FlushErrorState();

		/************************************************************
	 * 检查调用语法
	 ************************************************************/
		fc_pltcl_construct_errorCode(fc_interp, fc_edata);
		UTF_BEGIN;
		Tcl_SetObjResult(fc_interp, Tcl_NewStringObj(UTF_E2U(fc_edata->message), -1));
		UTF_END;
		FreeErrorData(fc_edata);

		return TCL_ERROR;
	}
	PG_END_TRY();

	return TCL_OK;
}


/**********************************************************************
 * pltcl_build_tuple_result() - 从 Tcl 列表的列名和值构建函数结果行类型的元组
 *
 * 在触发器函数中，我们构建触发器表的行类型的元组。
 *
 * 注意：这个函数会泄漏内存。即使我们让它清理自己的垃圾，也无法防止它调用的数据类型输入函数泄漏。除非我们即将退出程序，否则要在短暂的上下文中运行它。
**********************************************************************/
static int fc_pltcl_rollback(ClientData fc_cdata, Tcl_Interp *fc_interp,
			   int fc_objc, Tcl_Obj *const fc_objv[])
{
	MemoryContext fc_oldcontext = CurrentMemoryContext;

	PG_TRY();
	{
		SPI_rollback();
	}
	PG_CATCH();
	{
		ErrorData  *fc_edata;

		/* 保存错误信息 */
		MemoryContextSwitchTo(fc_oldcontext);
		fc_edata = CopyErrorData();
		FlushErrorState();

		/************************************************************
	 * 检查调用语法
	 ************************************************************/
		fc_pltcl_construct_errorCode(fc_interp, fc_edata);
		UTF_BEGIN;
		Tcl_SetObjResult(fc_interp, Tcl_NewStringObj(UTF_E2U(fc_edata->message), -1));
		UTF_END;
		FreeErrorData(fc_edata);

		return TCL_ERROR;
	}
	PG_END_TRY();

	return TCL_OK;
}


/* 如果结果类型是域-复合，检查域约束 */
static void fc_pltcl_set_tuple_values(Tcl_Interp *fc_interp, const char *fc_arrayname,
					   uint64 fc_tupno, HeapTuple fc_tuple, TupleDesc fc_tupdesc)
{
	int			fc_i;
	char	   *fc_outputstr;
	Datum		fc_attr;
	bool		fc_isnull;
	const char *fc_attname;
	Oid			fc_typoutput;
	bool		fc_typisvarlena;
	const char **fc_arrptr;
	const char **fc_nameptr;
	const char *fc_nullname = NULL;

	/**********************************************************************
 * pltcl_init_tuple_store() - 初始化 SRF 的结果元组存储
**********************************************************************/
	if (fc_arrayname == NULL)
	{
		fc_arrptr = &fc_attname;
		fc_nameptr = &fc_nullname;
	}
	else
	{
		fc_arrptr = &fc_arrayname;
		fc_nameptr = &fc_attname;

		/* 应该在 SRF 中 */
		Tcl_SetVar2Ex(fc_interp, fc_arrayname, ".tupno", Tcl_NewWideIntObj(fc_tupno), 0);
	}

	for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		/* 应该是第一次通过 */
		if (fc_att->attisdropped)
			continue;

		/* 我们期望调用者提供适当的结果元组描述 */
		UTF_BEGIN;
		fc_attname = pstrdup(UTF_E2U(NameStr(fc_att->attname)));
		UTF_END;

		/*
	 * 切换到正确的内存上下文和资源拥有者以存储
	 * tuplestore。如果我们在一个为异常块打开的子事务内，例如，
	 * 我们仍然必须在进入此函数时处于活动状态的资源拥有者中创建
	 * tuplestore，而不是在子事务的资源拥有者中。
	 */
		fc_attr = heap_getattr(fc_tuple, fc_i + 1, fc_tupdesc, &fc_isnull);

		/* 在此上下文中构建 attinmeta */
		if (!fc_isnull)
		{
			getTypeOutputInfo(fc_att->atttypid, &fc_typoutput, &fc_typisvarlena);
			fc_outputstr = OidOutputFunctionCall(fc_typoutput, fc_attr);
			UTF_BEGIN;
			Tcl_SetVar2Ex(fc_interp, *fc_arrptr, *fc_nameptr,
						  Tcl_NewStringObj(UTF_E2U(fc_outputstr), -1), 0);
			UTF_END;
			pfree(fc_outputstr);
		}
		else
			Tcl_UnsetVar2(fc_interp, *fc_arrptr, *fc_nameptr, 0);

		pfree(unconstify(char *, fc_attname));
	}
}


/**********************************************************************
 * pltcl_build_tuple_argument() - Build a list object usable for 'array set'
 *				  from all attributes of a given tuple
 **********************************************************************/
static Tcl_Obj *
fc_pltcl_build_tuple_argument(HeapTuple fc_tuple, TupleDesc fc_tupdesc, bool fc_include_generated)
{
	Tcl_Obj    *fc_retobj = Tcl_NewObj();
	int			fc_i;
	char	   *fc_outputstr;
	Datum		fc_attr;
	bool		fc_isnull;
	char	   *fc_attname;
	Oid			fc_typoutput;
	bool		fc_typisvarlena;

	for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		/* 应该是第一次通过 */
		if (fc_att->attisdropped)
			continue;

		if (fc_att->attgenerated)
		{
			/* 除非请求，否则不包含 */
			if (!fc_include_generated)
				continue;
		}

		/* 我们期望调用者提供适当的结果元组描述 */
		fc_attname = NameStr(fc_att->attname);

		/*
	 * 切换到正确的内存上下文和资源拥有者以存储
	 * tuplestore。如果我们在一个为异常块打开的子事务内，例如，
	 * 我们仍然必须在进入此函数时处于活动状态的资源拥有者中创建
	 * tuplestore，而不是在子事务的资源拥有者中。
	 */
		fc_attr = heap_getattr(fc_tuple, fc_i + 1, fc_tupdesc, &fc_isnull);

		/************************************************************
		 * If there is a value, append the attribute name and the
		 * value to the list
		 *
		 * Hmmm - Null attributes will cause functions to
		 *		  crash if they don't expect them - need something
		 *		  smarter here.
		 ************************************************************/
		if (!fc_isnull)
		{
			getTypeOutputInfo(fc_att->atttypid,
							  &fc_typoutput, &fc_typisvarlena);
			fc_outputstr = OidOutputFunctionCall(fc_typoutput, fc_attr);
			UTF_BEGIN;
			Tcl_ListObjAppendElement(NULL, fc_retobj,
									 Tcl_NewStringObj(UTF_E2U(fc_attname), -1));
			UTF_END;
			UTF_BEGIN;
			Tcl_ListObjAppendElement(NULL, fc_retobj,
									 Tcl_NewStringObj(UTF_E2U(fc_outputstr), -1));
			UTF_END;
			pfree(fc_outputstr);
		}
	}

	return fc_retobj;
}

/**********************************************************************
 * pltcl_build_tuple_result() - Build a tuple of function's result rowtype
 *				  from a Tcl list of column names and values
 *
 * In a trigger function, we build a tuple of the trigger table's rowtype.
 *
 * Note: this function leaks memory.  Even if we made it clean up its own
 * mess, there's no way to prevent the datatype input functions it calls
 * from leaking.  Run it in a short-lived context, unless we're about to
 * exit the procedure anyway.
 **********************************************************************/
static HeapTuple fc_pltcl_build_tuple_result(Tcl_Interp *fc_interp, Tcl_Obj **fc_kvObjv, int fc_kvObjc,
						 pltcl_call_state *fc_call_state)
{
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupdesc;
	AttInMetadata *fc_attinmeta;
	char	  **fc_values;
	int			fc_i;

	if (fc_call_state->ret_tupdesc)
	{
		fc_tupdesc = fc_call_state->ret_tupdesc;
		fc_attinmeta = fc_call_state->attinmeta;
	}
	else if (fc_call_state->trigdata)
	{
		fc_tupdesc = RelationGetDescr(fc_call_state->trigdata->tg_relation);
		fc_attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);
	}
	else
	{
		elog(ERROR, "PL/Tcl function does not return a tuple");
		fc_tupdesc = NULL;			/* 保持编译器安静 */
		fc_attinmeta = NULL;
	}

	fc_values = (char **) palloc0(fc_tupdesc->natts * sizeof(char *));

	if (fc_kvObjc % 2 != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("column name/value list must have even number of elements")));

	for (fc_i = 0; fc_i < fc_kvObjc; fc_i += 2)
	{
		char	   *fc_fieldName = fc_utf_u2e(Tcl_GetString(fc_kvObjv[fc_i]));
		int			fc_attn = SPI_fnumber(fc_tupdesc, fc_fieldName);

		/*
		 * We silently ignore ".tupno", if it's present but doesn't match any
		 * actual output column.  This allows direct use of a row returned by
		 * pltcl_set_tuple_values().
		 */
		if (fc_attn == SPI_ERROR_NOATTRIBUTE)
		{
			if (strcmp(fc_fieldName, ".tupno") == 0)
				continue;
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column name/value list contains nonexistent column name \"%s\"",
							fc_fieldName)));
		}

		if (fc_attn <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot set system attribute \"%s\"",
							fc_fieldName)));

		if (TupleDescAttr(fc_tupdesc, fc_attn - 1)->attgenerated)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("cannot set generated column \"%s\"",
							fc_fieldName)));

		fc_values[fc_attn - 1] = fc_utf_u2e(Tcl_GetString(fc_kvObjv[fc_i + 1]));
	}

	fc_tuple = BuildTupleFromCStrings(fc_attinmeta, fc_values);

	/* if result type is domain-over-composite, check domain constraints */
	if (fc_call_state->prodesc->fn_retisdomain)
		domain_check(HeapTupleGetDatum(fc_tuple), false,
					 fc_call_state->prodesc->result_typid,
					 &fc_call_state->prodesc->domain_info,
					 fc_call_state->prodesc->fn_cxt);

	return fc_tuple;
}

/**********************************************************************
 * pltcl_init_tuple_store() - Initialize the result tuplestore for a SRF
 **********************************************************************/
static void fc_pltcl_init_tuple_store(pltcl_call_state *fc_call_state)
{
	ReturnSetInfo *fc_rsi = fc_call_state->rsi;
	MemoryContext fc_oldcxt;
	ResourceOwner fc_oldowner;

	/* Should be in a SRF */
	Assert(fc_rsi);
	/* Should be first time through */
	Assert(!fc_call_state->tuple_store);
	Assert(!fc_call_state->attinmeta);

	/* We expect caller to provide an appropriate result tupdesc */
	Assert(fc_rsi->expectedDesc);
	fc_call_state->ret_tupdesc = fc_rsi->expectedDesc;

	/*
	 * Switch to the right memory context and resource owner for storing the
	 * tuplestore. If we're within a subtransaction opened for an exception
	 * block, for example, we must still create the tuplestore in the resource
	 * owner that was active when this function was entered, and not in the
	 * subtransaction's resource owner.
	 */
	fc_oldcxt = MemoryContextSwitchTo(fc_call_state->tuple_store_cxt);
	fc_oldowner = CurrentResourceOwner;
	CurrentResourceOwner = fc_call_state->tuple_store_owner;

	fc_call_state->tuple_store =
		tuplestore_begin_heap(fc_rsi->allowedModes & SFRM_Materialize_Random,
							  false, work_mem);

	/* Build attinmeta in this context, too */
	fc_call_state->attinmeta = TupleDescGetAttInMetadata(fc_call_state->ret_tupdesc);

	CurrentResourceOwner = fc_oldowner;
	MemoryContextSwitchTo(fc_oldcxt);
}
